{
  "openapi": "3.1.0",
  "info": {
    "title": "uvrouter API",
    "version": "1.0.0",
    "summary": "Swap quotes and executable swap transactions on BNB Chain.",
    "description": "Exact-input swap quotes and ready-to-send UvRouter transactions on BNB Chain (chainId 56). No API key. Human docs: https://uvstack.xyz/docs/. /v1 is stable: breaking changes only ship as /v2, announced at least 3 weeks ahead on the docs page.",
    "contact": {
      "name": "uvstack",
      "url": "https://t.me/uvstarr1"
    }
  },
  "externalDocs": {
    "description": "uvrouter API docs",
    "url": "https://uvstack.xyz/docs/"
  },
  "servers": [
    {
      "url": "https://router.uvstack.xyz"
    }
  ],
  "paths": {
    "/v1/quote": {
      "get": {
        "operationId": "getQuote",
        "summary": "Quote an exact-input swap",
        "description": "Price for selling sellAmount of sellToken for buyToken. buyAmount is already net of the fee (feeBps) and has no slippage cushion. Nothing is reserved or signed. The route is always a single path over the venues /v1/swap executes (PancakeSwap V2, Uniswap V2, PancakeSwap V3, Uniswap V3), so every quote can be built as a swap.",
        "parameters": [
          {
            "name": "chainId",
            "in": "query",
            "required": true,
            "description": "Chain id. Only 56 (BNB Smart Chain).",
            "schema": {
              "type": "integer",
              "enum": [
                56
              ]
            },
            "example": 56
          },
          {
            "name": "sellToken",
            "in": "query",
            "required": true,
            "description": "Token sold. One of the supported tokens, or native BNB written 0xEeee...EEeE or the zero address: priced as WBNB and echoed back as sent.",
            "schema": {
              "type": "string",
              "pattern": "^0x[0-9a-fA-F]{40}$"
            },
            "example": "0x55d398326f99059fF775485246999027B3197955"
          },
          {
            "name": "buyToken",
            "in": "query",
            "required": true,
            "description": "Token bought. Same rules as sellToken; must differ from it (native BNB and WBNB count as the same).",
            "schema": {
              "type": "string",
              "pattern": "^0x[0-9a-fA-F]{40}$"
            },
            "example": "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"
          },
          {
            "name": "sellAmount",
            "in": "query",
            "required": true,
            "description": "Exact input in base units of sellToken, a decimal integer from 1 to 340282366920938463463374607431768211455 (2^128-1).",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{1,39}$"
            },
            "example": "1000000000000000000"
          }
        ],
        "responses": {
          "200": {
            "description": "A quote.",
            "headers": {
              "Cache-Control": {
                "description": "Always no-store.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                },
                "example": {
                  "chainId": 56,
                  "sellToken": "0x55d398326f99059ff775485246999027b3197955",
                  "buyToken": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c",
                  "sellAmount": "1000000000000000000",
                  "buyAmount": "1670076516394888",
                  "feeBps": 2,
                  "route": {
                    "hops": [
                      {
                        "venue": "0xb971ef87ede563556b2ed4b1c0b0019111dd85d2",
                        "tokenIn": "0x55d398326f99059ff775485246999027b3197955",
                        "tokenOut": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c"
                      }
                    ]
                  },
                  "gasEstimate": "316000",
                  "quoteId": "1a1da726b76-14",
                  "validFor": 15
                }
              }
            }
          },
          "400": {
            "description": "INVALID_REQUEST, UNSUPPORTED_CHAIN, INVALID_TOKEN, UNSUPPORTED_TOKEN or INVALID_AMOUNT.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "UNSUPPORTED_CHAIN",
                    "message": "chainId 1 is not served"
                  }
                }
              }
            }
          },
          "404": {
            "description": "NOT_FOUND. Unknown path.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "NOT_FOUND",
                    "message": "no such endpoint"
                  }
                }
              }
            }
          },
          "422": {
            "description": "NO_ROUTE (no single-path route over the supported venues, or the output rounds to zero) or NO_EXECUTABLE_ROUTE (the route found cannot be executed by the contract). A valid request with no answer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "NO_ROUTE",
                    "message": "no route found for this pair and amount"
                  }
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED. Retry-After in seconds.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "RATE_LIMITED",
                    "message": "rate limited, retry in 3s"
                  }
                }
              }
            }
          },
          "502": {
            "description": "UPSTREAM_ERROR. The routing backend is down or answered something unusable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "UPSTREAM_ERROR",
                    "message": "quote backend unavailable"
                  }
                }
              }
            }
          },
          "503": {
            "description": "BUSY. The concurrency cap (4 quotes at once) is full; answered at once. Retry-After: 1.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "BUSY",
                    "message": "too many quotes in flight, retry shortly"
                  }
                }
              }
            }
          },
          "504": {
            "description": "TIMEOUT. The quote did not finish in 950 ms.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "TIMEOUT",
                    "message": "the quote did not complete in time"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/swap": {
      "get": {
        "operationId": "getSwap",
        "summary": "Quote plus the transaction that executes it",
        "description": "Same quote as /v1/quote plus a transaction for the UvRouter contract. Permit2 flow (EOA takers, default): approve Permit2 for sellToken, sign permit2.typedData with eth_signTypedData_v4 and write the 65-byte signature into tx.data at permit2.signatureOffset. ERC-20 allowance flow (approval=erc20, default for contract takers, approve allowance.spender (the router) for allowance.amount and send tx; nothing is signed. For native BNB in, send tx as returned (permit2 and allowance are null). minOut is enforced on chain; the transaction reverts after deadline. A sellAmount above the sell token's per-swap cap is refused with INVALID_AMOUNT.",
        "parameters": [
          {
            "name": "chainId",
            "in": "query",
            "required": true,
            "description": "Chain id. Only 56 (BNB Smart Chain).",
            "schema": {
              "type": "integer",
              "enum": [
                56
              ]
            },
            "example": 56
          },
          {
            "name": "sellToken",
            "in": "query",
            "required": true,
            "description": "Token sold. One of the supported tokens, or native BNB written 0xEeee...EEeE or the zero address: priced as WBNB and echoed back as sent.",
            "schema": {
              "type": "string",
              "pattern": "^0x[0-9a-fA-F]{40}$"
            },
            "example": "0x55d398326f99059fF775485246999027B3197955"
          },
          {
            "name": "buyToken",
            "in": "query",
            "required": true,
            "description": "Token bought. Same rules as sellToken; must differ from it (native BNB and WBNB count as the same).",
            "schema": {
              "type": "string",
              "pattern": "^0x[0-9a-fA-F]{40}$"
            },
            "example": "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"
          },
          {
            "name": "sellAmount",
            "in": "query",
            "required": true,
            "description": "Exact input in base units of sellToken, a decimal integer from 1 to 340282366920938463463374607431768211455 (2^128-1).",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{1,39}$"
            },
            "example": "1000000000000000000"
          },
          {
            "name": "taker",
            "in": "query",
            "required": true,
            "description": "The payer: signs the Permit2 permit, or sends the BNB. Not the zero address.",
            "schema": {
              "type": "string",
              "pattern": "^0x[0-9a-fA-F]{40}$"
            },
            "example": "0x000000000000000000000000000000000000dEaD"
          },
          {
            "name": "recipient",
            "in": "query",
            "required": false,
            "description": "Receives the output. Default: taker. Not the zero address and not the UvRouter contract.",
            "schema": {
              "type": "string",
              "pattern": "^0x[0-9a-fA-F]{40}$"
            }
          },
          {
            "name": "slippageBps",
            "in": "query",
            "required": false,
            "description": "Basis points under buyAmount used for minOut.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 500,
              "default": 50
            }
          },
          {
            "name": "approval",
            "in": "query",
            "required": false,
            "description": "How an ERC-20 sell is authorised; ignored for native in. permit2: sign a Permit2 witness. erc20: approve UvRouter and send swapWithAllowance. Default: erc20 when taker is a contract, else permit2 (EOAs and EIP-7702 accounts).",
            "schema": {
              "type": "string",
              "enum": [
                "permit2",
                "erc20"
              ]
            }
          },
          {
            "name": "nonce",
            "in": "query",
            "required": false,
            "description": "Permit2 unordered nonce (decimal, up to 256 bits) not yet used by the taker. Default: generated and unique per response.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{1,78}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A quote and a transaction.",
            "headers": {
              "Cache-Control": {
                "description": "Always no-store.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SwapQuote"
                },
                "example": {
                  "chainId": 56,
                  "sellToken": "0x55d398326f99059ff775485246999027b3197955",
                  "buyToken": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c",
                  "sellAmount": "1000000000000000000",
                  "buyAmount": "1667792331532151",
                  "feeBps": 2,
                  "route": {
                    "hops": [
                      {
                        "venue": "0xb971ef87ede563556b2ed4b1c0b0019111dd85d2",
                        "tokenIn": "0x55d398326f99059ff775485246999027b3197955",
                        "tokenOut": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c"
                      }
                    ]
                  },
                  "gasEstimate": "316000",
                  "quoteId": "1a1da726b76-9",
                  "validFor": 15,
                  "minOut": "1659453369874490",
                  "slippageBps": 50,
                  "deadline": 1794667043,
                  "tx": {
                    "to": "0x8e625bc6786f8dda4dbc23bdea0b9ab7c625b893",
                    "data": "0xf4af5da500000000000000000000000055d398326f99059ff775485246999027b3197955...",
                    "value": "0",
                    "gas": "413750"
                  },
                  "permit2": {
                    "typedData": {
                      "domain": {
                        "chainId": 56,
                        "name": "Permit2",
                        "verifyingContract": "0x000000000022d473030f116ddee9f6b43ac78ba3"
                      },
                      "message": {
                        "deadline": "1794667043",
                        "nonce": "1305543742891162411723926365419359454820703564147307053065",
                        "permitted": {
                          "amount": "1000000000000000000",
                          "token": "0x55d398326f99059ff775485246999027b3197955"
                        },
                        "spender": "0x8e625bc6786f8dda4dbc23bdea0b9ab7c625b893",
                        "witness": {
                          "feeBps": "2",
                          "hops": [
                            {
                              "fee": 100,
                              "kind": 1,
                              "tokenOut": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c",
                              "venue": 1
                            }
                          ],
                          "minOut": "1659453369874490",
                          "recipient": "0x000000000000000000000000000000000000dead",
                          "tokenOut": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c",
                          "unwrapNative": false
                        }
                      },
                      "primaryType": "PermitWitnessTransferFrom",
                      "types": {
                        "EIP712Domain": [
                          {
                            "name": "name",
                            "type": "string"
                          },
                          {
                            "name": "chainId",
                            "type": "uint256"
                          },
                          {
                            "name": "verifyingContract",
                            "type": "address"
                          }
                        ],
                        "Hop": [
                          {
                            "name": "kind",
                            "type": "uint8"
                          },
                          {
                            "name": "venue",
                            "type": "uint8"
                          },
                          {
                            "name": "fee",
                            "type": "uint24"
                          },
                          {
                            "name": "tokenOut",
                            "type": "address"
                          }
                        ],
                        "PermitWitnessTransferFrom": [
                          {
                            "name": "permitted",
                            "type": "TokenPermissions"
                          },
                          {
                            "name": "spender",
                            "type": "address"
                          },
                          {
                            "name": "nonce",
                            "type": "uint256"
                          },
                          {
                            "name": "deadline",
                            "type": "uint256"
                          },
                          {
                            "name": "witness",
                            "type": "Swap"
                          }
                        ],
                        "Swap": [
                          {
                            "name": "tokenOut",
                            "type": "address"
                          },
                          {
                            "name": "minOut",
                            "type": "uint256"
                          },
                          {
                            "name": "recipient",
                            "type": "address"
                          },
                          {
                            "name": "unwrapNative",
                            "type": "bool"
                          },
                          {
                            "name": "feeBps",
                            "type": "uint256"
                          },
                          {
                            "name": "hops",
                            "type": "Hop[]"
                          }
                        ],
                        "TokenPermissions": [
                          {
                            "name": "token",
                            "type": "address"
                          },
                          {
                            "name": "amount",
                            "type": "uint256"
                          }
                        ]
                      }
                    },
                    "signatureOffset": 612
                  }
                }
              }
            }
          },
          "400": {
            "description": "INVALID_REQUEST, UNSUPPORTED_CHAIN, INVALID_TOKEN, UNSUPPORTED_TOKEN or INVALID_AMOUNT.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "UNSUPPORTED_CHAIN",
                    "message": "chainId 1 is not served"
                  }
                }
              }
            }
          },
          "404": {
            "description": "NOT_FOUND. Unknown path. Also /v1/swap while it is switched off, for example right after a restart.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "NOT_FOUND",
                    "message": "no such endpoint"
                  }
                }
              }
            }
          },
          "422": {
            "description": "NO_ROUTE (no single-path route over the supported venues, or the output rounds to zero) or NO_EXECUTABLE_ROUTE (the route found cannot be executed by the contract). A valid request with no answer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "NO_ROUTE",
                    "message": "no route found for this pair and amount"
                  }
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED. Retry-After in seconds.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "RATE_LIMITED",
                    "message": "rate limited, retry in 3s"
                  }
                }
              }
            }
          },
          "502": {
            "description": "UPSTREAM_ERROR. The routing backend is down or answered something unusable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "UPSTREAM_ERROR",
                    "message": "quote backend unavailable"
                  }
                }
              }
            }
          },
          "503": {
            "description": "BUSY. The concurrency cap (4 quotes at once) is full; answered at once. Retry-After: 1.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "BUSY",
                    "message": "too many quotes in flight, retry shortly"
                  }
                }
              }
            }
          },
          "504": {
            "description": "TIMEOUT. The quote did not finish in 950 ms.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "TIMEOUT",
                    "message": "the quote did not complete in time"
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Address": {
        "type": "string",
        "pattern": "^0x[0-9a-fA-F]{40}$",
        "description": "Lowercase in responses."
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "INVALID_REQUEST",
                  "UNSUPPORTED_CHAIN",
                  "INVALID_TOKEN",
                  "UNSUPPORTED_TOKEN",
                  "INVALID_AMOUNT",
                  "RATE_LIMITED",
                  "BUSY",
                  "NO_ROUTE",
                  "NO_EXECUTABLE_ROUTE",
                  "TIMEOUT",
                  "UPSTREAM_ERROR",
                  "NOT_FOUND"
                ],
                "description": "Stable. New codes may be added in /v1; handle an unknown one by its HTTP status."
              },
              "message": {
                "type": "string",
                "description": "For humans; may change."
              }
            }
          }
        }
      },
      "Hop": {
        "type": "object",
        "required": [
          "venue"
        ],
        "properties": {
          "venue": {
            "type": "string",
            "description": "Router or pool contract address."
          },
          "tokenIn": {
            "$ref": "#/components/schemas/Address"
          },
          "tokenOut": {
            "$ref": "#/components/schemas/Address"
          }
        },
        "description": "tokenIn and tokenOut are omitted on a hop that has none (for example a wrap)."
      },
      "Quote": {
        "type": "object",
        "required": [
          "chainId",
          "sellToken",
          "buyToken",
          "sellAmount",
          "buyAmount",
          "feeBps",
          "route",
          "gasEstimate",
          "quoteId",
          "validFor"
        ],
        "properties": {
          "chainId": {
            "type": "integer",
            "const": 56
          },
          "sellToken": {
            "$ref": "#/components/schemas/Address"
          },
          "buyToken": {
            "$ref": "#/components/schemas/Address"
          },
          "sellAmount": {
            "type": "string",
            "description": "Base units."
          },
          "buyAmount": {
            "type": "string",
            "description": "Expected output in base units of buyToken, after the fee, before any slippage cushion. Gas is not netted out."
          },
          "feeBps": {
            "type": "integer",
            "minimum": 0,
            "maximum": 50,
            "description": "Fee already taken out of buyAmount, in basis points. Chosen per request. The contract refuses above 50."
          },
          "route": {
            "type": "object",
            "required": [
              "hops"
            ],
            "properties": {
              "hops": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Hop"
                }
              }
            },
            "description": "Informational. Always one path, the one /v1/swap executes."
          },
          "gasEstimate": {
            "type": [
              "string",
              "null"
            ],
            "description": "Engine gas estimate for the route. Use tx.gas on /v1/swap for the transaction."
          },
          "quoteId": {
            "type": "string"
          },
          "validFor": {
            "type": "integer",
            "description": "Seconds the price is worth acting on (15)."
          }
        }
      },
      "Tx": {
        "type": "object",
        "required": [
          "to",
          "data",
          "value",
          "gas"
        ],
        "properties": {
          "to": {
            "$ref": "#/components/schemas/Address"
          },
          "data": {
            "type": "string",
            "pattern": "^0x([0-9a-f]{2})*$",
            "description": "Calldata. On the permit2 flow it holds a 65-byte zero placeholder at permit2.signatureOffset."
          },
          "value": {
            "type": "string",
            "description": "Wei. sellAmount for native in, else \"0\"."
          },
          "gas": {
            "type": "string",
            "description": "Gas limit with about 25% headroom."
          }
        }
      },
      "Permit2Part": {
        "type": "object",
        "required": [
          "typedData",
          "signatureOffset"
        ],
        "properties": {
          "typedData": {
            "type": "object",
            "description": "EIP-712 payload for eth_signTypedData_v4: Permit2 PermitWitnessTransferFrom with the Swap witness (tokenOut, minOut, recipient, unwrapNative, feeBps, hops).",
            "required": [
              "domain",
              "primaryType",
              "types",
              "message"
            ],
            "properties": {
              "domain": {
                "type": "object"
              },
              "primaryType": {
                "type": "string",
                "const": "PermitWitnessTransferFrom"
              },
              "types": {
                "type": "object"
              },
              "message": {
                "type": "object"
              }
            }
          },
          "signatureOffset": {
            "type": "integer",
            "description": "Byte offset in the decoded tx.data where the 65-byte signature (r||s||v, v 27 or 28) goes. For a signature of another length (EIP-1271, EIP-7702), replace everything from signatureOffset - 32 with the ABI encoding of the bytes."
          }
        }
      },
      "SwapQuote": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Quote"
          },
          {
            "type": "object",
            "required": [
              "minOut",
              "slippageBps",
              "deadline",
              "tx",
              "permit2",
              "allowance"
            ],
            "properties": {
              "minOut": {
                "type": "string",
                "description": "Least the recipient receives: buyAmount x (10000 - slippageBps) / 10000, rounded down. Enforced on chain."
              },
              "slippageBps": {
                "type": "integer"
              },
              "deadline": {
                "type": "integer",
                "description": "Unix seconds (120 s after the response). The permit and the transaction expire then."
              },
              "tx": {
                "$ref": "#/components/schemas/Tx"
              },
              "permit2": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/Permit2Part"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "null for native BNB in and for approval=erc20."
              },
              "allowance": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/AllowancePart"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "The ERC-20 approval the taker needs before sending tx. null for native BNB in."
              },
              "gasEstimate": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Expected gas of tx (replaces the quote's figure). tx.gas is this plus 25%."
              }
            }
          }
        ],
        "description": "A native sellToken or buyToken is echoed as sent; with a native buyToken the recipient receives BNB."
      },
      "AllowancePart": {
        "type": "object",
        "required": [
          "token",
          "spender",
          "amount"
        ],
        "properties": {
          "token": {
            "$ref": "#/components/schemas/Address"
          },
          "spender": {
            "$ref": "#/components/schemas/Address",
            "description": "Permit2 on the permit2 flow, the UvRouter on the erc20 flow."
          },
          "amount": {
            "type": "string",
            "description": "Least allowance needed, base units. The erc20 flow pulls exactly this."
          }
        }
      }
    }
  }
}
