API Integration
The CoW Protocol API provides direct access to the protocol's functionality through RESTful endpoints. This approach offers maximum flexibility and control for backend integrations and custom implementations.
Overview
The API integration allows you to interact with CoW Protocol at the lowest level, giving you complete control over order management, quote fetching, and trade execution. This is ideal for advanced integrations, backend services, and custom trading logic.
Key APIs
Order Book API
The primary API for creating and managing orders on CoW Protocol.
- Base URL:
https://api.cow.fi/ - Purpose: Quote generation, order submission, order management
- Authentication: No API key required for basic operations
Key Endpoints
POST /api/v1/quote- Get trading quotesPOST /api/v1/quote/stream- Stream progressively improving quotes as solvers respond (SSE)POST /api/v1/orders- Submit signed ordersGET /api/v1/orders/{uid}- Get order detailsDELETE /api/v1/orders/{uid}- Cancel ordersGET /api/v1/trades- Get trade history
Quick Start Example
1. Get a Quote
curl -X POST "https://api.cow.fi/mainnet/api/v1/quote" \
-H "Content-Type: application/json" \
-d '{
"sellToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"buyToken": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"sellAmountBeforeFee": "1000000",
"kind": "sell",
"from": "0xYourWalletAddress"
}'
For sell orders (kind: "sell"), quote.buyAmount is the quoted output after estimated network costs (gas) and the applicable protocol fee have been deducted. Do not subtract these costs again or compare this amount directly with another provider's before-gas output.
See Displaying and Comparing Quotes for a worked example and calculations using the API response.
2. Sign and Submit Order
Build the order from quoteResponse.quote, not from the whole response. For a sell order, sign the full sell amount with feeAmount: "0", and reduce the buy amount by any partner fee and then the slippage tolerance (see Forming the Final Order).
If the quote request includes full appData JSON, sign quote.appDataHash and send the JSON as appData.
The example below assumes no partner fee.
const { quote } = quoteResponse
const slippageBps = 50n // 0.5%
const order = {
sellToken: quote.sellToken,
buyToken: quote.buyToken,
receiver: quote.receiver,
// quote.sellAmount is after network costs; sign the full amount
sellAmount: (BigInt(quote.sellAmount) + BigInt(quote.feeAmount)).toString(),
// minimum to receive
buyAmount: (BigInt(quote.buyAmount) * (10000n - slippageBps) / 10000n).toString(),
validTo: quote.validTo,
appData: quote.appDataHash ?? quote.appData, // signed as the bytes32 hash
feeAmount: "0", // network costs are taken from sellAmount at settlement
kind: quote.kind,
partiallyFillable: quote.partiallyFillable,
sellTokenBalance: quote.sellTokenBalance,
buyTokenBalance: quote.buyTokenBalance,
}
// Submit the signed order (see Order Signing below)
const response = await fetch('https://api.cow.fi/mainnet/api/v1/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...order,
appData: quote.appData, // full app data JSON, if supplied in the quote request
from: quoteResponse.from,
quoteId: quoteResponse.id,
signature: await signOrder(order, signer),
signingScheme: "eip712",
})
})
const orderId = await response.text()
3. Monitor Order Status
// Check order status
const orderResponse = await fetch(
`https://api.cow.fi/mainnet/api/v1/orders/${orderId}`
)
const orderDetails = await orderResponse.json()
console.log('Order status:', orderDetails.status)
Displaying and Comparing Quotes
For a sell order, /quote returns amounts after network costs:
| Field | Meaning |
|---|---|
quote.sellAmount | Sell amount after network costs (sellAmountBeforeFee - feeAmount) |
quote.feeAmount | Estimated network costs, in sell token atoms |
quote.buyAmount | Buy amount after network costs and the protocol fee, before any partner fee |
Settlement network costs are already reflected in quote.buyAmount; quote.feeAmount describes those costs in the sell token, not an additional amount to deduct from the output.
Other aggregators usually return their output before gas, which the user pays on top.
Shown side by side as-is, CoW Protocol ranks lower even at the same price.
Compare every source on the same basis and for the same total sell input. Convert separately paid gas to buy-token units before comparing output amounts:
| Basis | CoW Protocol (sell order) | Other aggregators |
|---|---|---|
| Before gas | quote.buyAmount + network costs in buy token − partner fee | Their output |
| After gas | quote.buyAmount − partner fee | Their output − their gas |
The amounts are estimates, not guaranteed execution amounts. Execution can improve on the quote through solver competition and changes in network costs.
Example
Selling 10 USDT for USDC at a 1:1 price, with 0.40 USDT of network costs and no protocol or partner fee (illustrative numbers).
/quote returns sellAmount: "9600000", feeAmount: "400000" and buyAmount: "9600000" (both tokens have 6 decimals).
Another aggregator quotes 9.99 USDC, and the user pays 0.40 USD of gas on top.
| CoW Protocol | Other aggregator | |
|---|---|---|
| Quoted output | 9.60 | 9.99 |
| Before gas | 9.60 + 0.40 = 10.00 | 9.99 |
| After gas | 9.60 | 9.99 − 0.40 = 9.59 |
CoW Protocol is better on both bases, but showing its raw quote.buyAmount next to the other output (9.60 vs 9.99) makes it look about 4% worse.
Calculating from the API response
For a sell quote, the following calculation uses only the returned amounts and any partner fee your integration charges. quote.feeAmount is in sell-token atoms, so do not add it directly to quote.buyAmount.
// quoteResponse is the JSON response from POST /api/v1/quote.
const { quote } = quoteResponse
if (quote.kind !== 'sell') {
throw new Error('This display calculation is for sell quotes')
}
const sellAmount = BigInt(quote.sellAmount)
const buyAmount = BigInt(quote.buyAmount)
const feeAmount = BigInt(quote.feeAmount)
const partnerFeeAmount = 0n // replace with your partner fee in buy-token atoms, if any
// Total sell-token input, including network costs.
const totalSellAmount = sellAmount + feeAmount
// Convert network costs to buy-token atoms at the quoted exchange rate.
const networkCostsInBuyToken = (buyAmount * feeAmount) / sellAmount
const beforeGas = buyAmount + networkCostsInBuyToken - partnerFeeAmount
const afterGas = buyAmount - partnerFeeAmount
For the example above, totalSellAmount is 10000000n (10 USDT), beforeGas is 10000000n (10 USDC), and afterGas is 9600000n (9.60 USDC).
Use totalSellAmount for the input display, beforeGas for a before-gas output display, and afterGas for comparison against other routes' output after costs.
Use integer arithmetic to avoid precision loss; division rounds down to whole buy-token atoms. Format the input with the sell token's decimals and the output with the buy token's decimals.
If your integration charges a partner fee, calculate its amount using the pre-fee quote value, not quote.buyAmount, and replace partnerFeeAmount with that amount in buy-token atoms.
The before-gas calculation keeps the protocol fee reflected in the output. Do not also add the protocol fee back when comparing with providers whose output includes their fees.
The before-gas amount is for display; do not use it as the order's signed buyAmount or as the expected amount received.
Buy orders
For a buy order, quote.buyAmount is the fixed amount the user receives, and quote.sellAmount includes the protocol fee but not network costs.
Before gas, compare quote.sellAmount plus any partner fee with other aggregators' input; after gas, also add quote.feeAmount.
For the same requested buy amount, prefer the lower estimated total input. Slippage tolerance increases the maximum authorized sell amount when signing; it is not an additional fee the user necessarily pays.
For every amount stage and its formula, see How Intents Are Formed.
If you use the SDK, getQuoteAmountsAndCosts provides these calculations and the amounts to sign.
Streaming Quotes
POST /api/v1/quote/stream takes the same request body as POST /api/v1/quote, but returns a Server-Sent Events stream instead of a single response.
The first event arrives as soon as the fastest solver responds, and after that a quote is only emitted when it ranks better than the best one already sent, so the client can show a price immediately and every update improves on the last.
Each event's data is an OrderQuoteResponse, the same shape POST /api/v1/quote returns.
The id on each event can be passed as quoteId when placing an order, the same as a quote from POST /api/v1/quote.
The endpoint always queries all solvers and attempts verification (the request's priceQuality is ignored), and each event carries its own verified flag.
A verified quote ranks above an unverified one regardless of the quoted amount, because unverified quotes often over-promise; a later event may therefore report a slightly worse price than an earlier one when it is the first verified quote.
Solvers without a usable quote, and quotes that do not beat the best already sent, emit no event, so the stream may contain fewer events than there are solvers.
Individual solver failures are not streamed; only if no solver returns a usable quote at all does the server send a final error event with a PriceEstimationError body (for example NoLiquidity), then close.
Because every event improves on the previous one, the last event received when the stream closes is the best available quote — clients do not need to rank events themselves.
curl -N -X POST "https://api.cow.fi/mainnet/api/v1/quote/stream" \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{
"sellToken": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"buyToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"sellAmountBeforeFee": "1000000000000000000",
"kind": "sell",
"from": "0xYourWalletAddress"
}'
Choosing a timeout
The first quote is delivered as soon as the fastest solver responds, so a usable price arrives faster than with POST /api/v1/quote, which waits for the whole competition.
Streaming does not make any solver compute faster, though.
The final quote is only as good as the responses that arrived before the stream was closed, and the timeout sets that boundary.
The stream stays open until the request timeout elapses or every solver has responded.
Slower solvers sometimes return a better price, especially on thin or unusual pairs that only one or two solvers can quote.
A short timeout returns sooner, but it can yield a worse quote or none at all.
A longer timeout gives the competition time to find a better price.
This tradeoff is controlled by the client through the timeout.
The value is set (in milliseconds) on the request, or the connection is closed once a good-enough quote has arrived.
The table below is a starting point, not a guarantee. The values should be measured against real traffic and raised for pairs that only slower solvers can price. Response times also differ by network, and these values shift as solver performance changes (guidance as of 2026-06).
| Network | timeout (ms) |
|---|---|
| Base, Gnosis Chain, Linea | 1000 |
| Mainnet, BNB Chain | 1800 |
| Arbitrum, Polygon, Avalanche, Ink | 2500 |
Network Endpoints
CoW Protocol APIs are available on multiple networks:
- Mainnet:
https://api.cow.fi/mainnet/api/v1/ - Gnosis Chain:
https://api.cow.fi/xdai/api/v1/ - Arbitrum:
https://api.cow.fi/arbitrum_one/api/v1/ - Base:
https://api.cow.fi/base/api/v1/ - Sepolia (Testnet):
https://api.cow.fi/sepolia/api/v1/
Order Signing
Orders must be cryptographically signed before submission. The signing process involves:
- EIP-712 Domain: Chain-specific domain separator
- Order Struct: Structured order data
- Signature: ECDSA signature or pre-signed order
import { ethers } from 'ethers'
// Example order signing with ethers.js
const domain = {
name: 'Gnosis Protocol',
version: 'v2',
chainId: 1,
verifyingContract: '0x9008D19f58AAbD9eD0D60971565AA8510560ab41'
}
const types = {
Order: [
{ name: 'sellToken', type: 'address' },
{ name: 'buyToken', type: 'address' },
{ name: 'receiver', type: 'address' },
// ... other order fields
]
}
const signature = await signer._signTypedData(domain, types, orderData)
Error Handling
The API returns standard HTTP status codes:
- 200: Success
- 400: Bad Request (invalid parameters)
- 404: Order not found
- 429: Rate limited
- 500: Internal server error
try {
const response = await fetch(apiUrl, requestOptions)
if (!response.ok) {
const error = await response.json()
throw new Error(`API Error: ${error.description}`)
}
return await response.json()
} catch (error) {
console.error('Order submission failed:', error)
}
When to Use the API
- Backend integration: Server-side order management
- Custom trading logic: Advanced order types and strategies
- High-frequency trading: Programmatic order placement
- Multi-chain applications: Cross-chain arbitrage and liquidity
- Analytics platforms: Order and trade data analysis
Rate Limits
- Quote requests: 10 requests/second
- Order submission: 5 requests/second
- General endpoints: 100 requests/minute
Next Steps
For complete API documentation including all endpoints, parameters, and response schemas, see:
- Order Book API Reference - Complete endpoint documentation
- API Documentation - Interactive API explorer
Resources
- Order Book API Reference - Detailed API documentation
- API Explorer - Interactive documentation
- GitHub Examples - Code examples
- Order Signing Guide - Cryptographic signing details
- How Intents Are Formed - Quote amount stages, fees, and the amounts to sign