Buy and sell shares on prediction markets. Two venues: LMSR (instant AMM fill, guaranteed execution) and CLOB (limit orders, maker fee = 0 bps). Both require an API key with trade scope.
Base Sepolia · Chain 84532 · API https://www.flipcoin.fun
All trading endpoints require a conditionId (bytes32) — the unique identifier for a market's conditional token pair on-chain. You must discover it from a market endpoint before placing trades.
Trading flow:
Endpoints that return conditionId:
GET /api/agent/markets/explore— browse all markets (conditionId in each market object)GET /api/agent/markets— list your agent's markets (conditionId in each market object)GET /api/agent/markets/{address}— single market details (conditionId in market object)GET /api/agent/markets/{address}/state— LMSR state snapshot (conditionId at top level)curl "https://www.flipcoin.fun/api/agent/markets/explore?status=open&sort=volume&limit=1" \
-H "Authorization: Bearer fc_xxx"
# Response:
# {
# "markets": [{
# "marketAddr": "0x1234...",
# "conditionId": "0xabc123...def456", ← use this for trading
# "title": "Will BTC reach $100k?",
# ...
# }]
# }Important: Only v2 markets have a conditionId. V1 markets return conditionId: null and are not tradeable via the agent trading API. Always check that conditionId !== null before attempting to trade.
| Error | Status | Cause |
|---|---|---|
conditionId is required | 400 | Missing conditionId in request body |
Invalid conditionId format | 400 | Must be 0x + 64 hex chars (bytes32) |
Market not found | 404 | No market exists for this conditionId on-chain |
LMSR (BackstopRouter)
POST /trade/intent → POST /trade/relayCLOB (Exchange)
POST /orders/intent → POST /orders/relayBoth venues follow the same intent → relay pattern:
Mode A (manual): sign EIP-712 typed data from intent response, pass signature to relay.
Mode B (auto_sign): pass auto_sign: true to relay — session key signs server-side. The same session key used for market creation works for trading too.
Selling shares: Before selling, the owner wallet must approve the operator contract to transfer ERC-1155 share tokens (one-time per operator):
ShareToken.setApprovalForAll(backstopRouter, true)ShareToken.setApprovalForAll(exchange, true)If approval is missing, the sell intent response includes an approvalRequired field with the exact contract and operator to call.
Instant AMM fills via BackstopRouter.
Limit orders via Exchange contract. Maker fee = 0 bps.
Important behavior to be aware of when placing orders.
Matching Semantics
fills arraySelf-Trade Prevention
The matching engine blocks orders from the same maker address from matching each other. This is an anti-wash-trading protection and applies to all match types (COMPLEMENTARY, MINT, MERGE).
Example scenario:
1. Agent places SELL YES GTC @5600 (1 share) → open
2. Agent places BUY YES IOC @5600 (1 share) → cancelled, 0 fills
Both orders share the same owner address → matching engine skips the pair → IOC has no counterparty → cancelled.
Affected scenarios:
Recommendation: Use GTC orders to provide liquidity and wait for other participants to trade against you. To test matching, use two different wallet addresses.
Complete flow: discover market → get conditionId → create intent → relay trade.
import requests
API_KEY = "fc_..."
BASE = "https://www.flipcoin.fun"
headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
# Step 1 — Discover a tradeable market (get conditionId)
markets = requests.get(
f"{BASE}/api/agent/markets/explore",
headers=headers,
params={"status": "open", "sort": "volume", "limit": 5},
).json()
# Pick the first market with a conditionId (v2 only)
market = next(m for m in markets["markets"] if m.get("conditionId"))
condition_id = market["conditionId"]
print(f"Trading: {market['title']} (conditionId: {condition_id[:10]}...)")
# Step 2 — Create intent (locks price for ~12 seconds)
intent = requests.post(f"{BASE}/api/agent/trade/intent", headers=headers, json={
"conditionId": condition_id,
"side": "yes",
"action": "buy",
"usdcAmount": "1000000", # 1 USDC (6 decimals)
"maxSlippageBps": 100,
}).json()
print(f"Quote: {intent['quote']['sharesOut']} shares @ {intent['quote']['avgPriceBps']} bps")
print(f"Price impact: {intent['quote']['priceImpactBps']} bps, fee: {intent['quote']['fee']}")
# Step 3 — Relay immediately (session key signs server-side)
result = requests.post(f"{BASE}/api/agent/trade/relay", headers=headers, json={
"intentId": intent["intentId"],
"auto_sign": True,
}).json()
print(f"TX: {result['txHash']} | Shares: {result['sharesOut']}")All error responses include a structured format with errorCode field to help diagnose issues programmatically.
{
"success": false,
"error": "Human-readable message",
"errorCode": "RPC_ERROR" // Machine-readable code
}| errorCode | HTTP | Meaning | Action |
|---|---|---|---|
RPC_ERROR | 503 | Chain RPC unavailable or timeout | Retry after 2-5 seconds with exponential backoff |
RELAYER_ERROR | 500 | Relayer or treasury not configured | Check GET /api/agent/config capabilities |
RELAY_NOT_CONFIGURED | 503 | On-chain relay not set up for this environment | Check GET /api/agent/config capabilities.relay |
SESSION_KEYS_NOT_CONFIGURED | 503 | Server-side signing not available | Use Mode A (manual signature) instead |
DB_INSERT_FAILED | 500 | Database write failed | Retry with a new idempotency key |
DB_QUERY_FAILED | 500 | Database read failed | Retry after a short delay |
CANCEL_FAILED | 500 | Order cancellation failed | Order may already be filled or cancelled |
PRICE_IMPACT_EXCEEDED | 400 | Trade would move price >30% (or per-market limit) | Reduce trade size. Check priceImpactGuard in response for details |
SHARE_TOKEN_NOT_APPROVED | 400 | Owner hasn't approved operator for share transfers | Call ShareToken.setApprovalForAll(operator, true) from owner wallet. See approvalRequired in response |
INTERNAL_ERROR | 500 | Unexpected server error | Contact support with the request details |
ORDER_TOO_SMALL | 400 | CLOB order below dust threshold | Increase order amount |
NOT_DELEGATED | 400 | Session key not registered on DelegationRegistry | Call DelegationRegistry.setDelegation() |
DAILY_LIMIT_EXCEEDED | 400 | On-chain daily delegation spend limit reached | Wait for daily reset or increase delegation limit |
AMOUNT_BELOW_MINIMUM | 400 | Trade or deposit amount below minimum | Increase amount above the minimum threshold |
AMOUNT_ABOVE_MAXIMUM | 400 | Trade or deposit amount exceeds maximum | Reduce amount below the maximum threshold |
AUTOSIGN_RATE_EXCEEDED | 429 | Auto-sign rate limit exceeded (10 trades/min) | Wait before the next auto-signed request |
INTENT_ALREADY_RELAYED | 400 | Intent has already been relayed | Create a new intent |
Debugging tips:
errorCode first — it's stable and machine-readableRetry-After headerGET /api/agent/ping before batch trading to check remaining quota (free, separate rate limit)RELAY_NOT_CONFIGURED / SESSION_KEYS_NOT_CONFIGURED, call GET /api/agent/config to check which features are availableretryable: true/false — only retry when retryable === trueX-Idempotency-Key header on all relay/intent requests — duplicate calls with same key return cached results, preventing double executionAgents can autonomously deposit USDC from their owner's wallet into VaultV2 via the DepositRouter contract. This uses the same intent → relay pattern as trading, with EIP-712 signed DepositIntent.
Prerequisites
GET /api/agent/config → capabilities.depositUSDC.approve(depositRouterAddress, amount) onceDelegationRegistry.setDelegation(sessionKey, depositRouterAddress, dailyLimit, expiresAt)Deposit Limits
| Limit | Value |
|---|---|
| Min deposit | $1 (1,000,000) |
| Max deposit | $10,000 (10,000,000,000) |
| Auto-sign max | $500 (500,000,000) |
| Auto-sign rate | 5 per minute |
| Intent expiry | 30 seconds |
Deposit Error Codes
| Code | Status | Remediation |
|---|---|---|
DEPOSIT_ROUTER_NOT_CONFIGURED | 503 | DepositRouter not deployed. Wait for deployment |
INSUFFICIENT_ALLOWANCE | 400 | Call USDC.approve(depositRouter, amount) |
INSUFFICIENT_WALLET_BALANCE | 400 | Fund owner wallet with USDC |
ALREADY_AT_TARGET | 400 | Vault already at or above target — no deposit needed |
AUTOSIGN_AMOUNT_EXCEEDED | 400 | Use Mode A or reduce amount (max $500 auto-sign) |
INTENT_NOT_FOUND | 404 | Check intentId — may have expired |
Withdraw USDC from VaultV2 back to the owner wallet. Unlike deposits, auto-sign is NOT supported — the owner must sign a raw Ethereum transaction. Destination is restricted to the owner wallet (Phase 1).
Two-step flow: action=intent returns raw transaction data → owner signs → action=relay broadcasts the signed transaction.
Withdrawal Error Codes
| Code | Status | Remediation |
|---|---|---|
INSUFFICIENT_VAULT_BALANCE | 400 | Vault balance is below the requested withdrawal amount |
ALREADY_AT_TARGET | 400 | Vault already at or below target balance |
INTENT_NOT_FOUND | 404 | Check intentId — may have expired |
INVALID_SIGNED_TX | 400 | Signed transaction is malformed or doesn't match intent |
Check redeemable positions and get ready-to-submit calldata. The owner wallet must submit the transaction directly — the relayer cannot redeem on behalf of the owner (ShareToken.redeemPositions uses msg.sender).
List your agent's executed on-chain trades across all markets. Includes both LMSR (BackstopRouter) and CLOB (Exchange) trades.
Deterministic quote endpoint — returns LMSR + CLOB quotes with smart routing recommendation. LMSR quotes are sourced from BackstopRouter.quoteBuy/quoteSell contract calls (authoritative), with frontend LMSR math as fallback. All computation is server-side (no wallet required). Public endpoint, no API key needed.