Connect a trading bot.
Endpoint Arena encourages algorithmic trading. Bring your own bot, test strategies against Season 5 markets, and use the API to quote prices, submit trades, and track results from your account.
How To Get Access
Create a normal Endpoint Arena account, complete Season 5 setup in the website, then create an API key from the API Access section of your profile.
The raw key is shown once. Keep it server-side, and delete it from your profile if it is exposed.
Your bot works with accounts, markets, quotes, trades, and statuses. Requests and responses stay at the product level.
Integration Flow
- Create a normal Endpoint Arena account and complete Season 5 setup in the website.
- Create an API key from the API Access section in your profile.
- Add mock funds and enable trading for your account.
- Call GET /api/v1/account and check readiness.canTrade.
- Call GET /api/v1/markets and pick a live marketId.
- Call POST /api/v1/quote with the exact trade body you intend to submit.
- Call POST /api/v1/trades with an Idempotency-Key. The response returns as soon as the trade is accepted.
- Poll GET /api/v1/trades/{tradeId} until the status is settled or failed.
Authentication
Every endpoint under /api/v1 requires a bearer token.
Authorization: Bearer <endpoint_arena_api_key> Content-Type: application/json Idempotency-Key: <unique key> # only for POST /api/v1/trades
Successful responses include an X-Request-Id header. Include that id when reporting integration issues.
Trade Semantics
Use the public marketId returned by the markets endpoint. Prices are probabilities from 0 to 1. Amounts are mock-USDC display values.
Spend amountUsd mock USDC to buy YES shares. Limited by your available cash balance.
Spend amountUsd mock USDC to buy NO shares. Limited by your available cash balance.
Sell enough YES shares to target amountUsd mock USDC proceeds. Limited by live YES share holdings.
Sell enough NO shares to target amountUsd mock USDC proceeds. Limited by live NO share holdings.
slippageBps is optional, defaults to 100, and can be 0 through 5000. Quote output gives the estimated shares or proceeds your bot can reason about directly.
Idempotency And Statuses
POST /api/v1/trades requires an Idempotency-Key header. Use a unique key per intended trade. Keys can be up to 200 characters.
Reusing the same key with the same normalized body returns the original trade and reused: true. Reusing the same key with a different body returns HTTP 409.
The trade was accepted and is being processed.
The trade has progressed but final balances may still be catching up.
The trade is final and account balances reflect it. Treat this as the final success state.
The trade could not be completed. Inspect errorCode and errorMessage on the trade.
Endpoints
Account readiness, mock-USDC cash balance, and setup flags.
Season 5 market summaries.
Market detail by market id.
Validate and price a proposed bot trade.
Submit a trade with an Idempotency-Key.
Poll trade status.
Trade Request Body
{
"marketId": "example-market",
"action": "BUY_YES",
"amountUsd": 10,
"slippageBps": 100
}For buys, amountUsd is spend. For sells, amountUsd is desired proceeds.
Response Shapes
GET /api/v1/account
{
"account": {
"id": "client_uuid",
"name": "acme-bot",
"status": "active",
"apiTradingEnabled": true
},
"balances": {
"cashUsd": 100
},
"readiness": {
"canTrade": true,
"needsAdminApproval": false,
"needsWebSetup": false,
"needsFunds": false,
"needsTradingSetup": false,
"setupUrl": "/profile",
"message": "Ready to trade through the API."
},
"environment": {
"season": "Season 5",
"mode": "mock-USDC sandbox",
"realMoney": false
}
}GET /api/v1/markets
Market objects include more trial fields than shown here. Use marketId when quoting or submitting a trade.
{
"markets": [
{
"marketId": "example-market",
"title": "Will the trial show a positive result on overall survival?",
"status": "deployed",
"prices": { "yes": 0.57, "no": 0.43 },
"activity": { "tradeCount": 42, "volumeUsd": 185.5, "lastTradeAt": "2026-04-26T12:00:00.000Z" },
"trial": { "sponsorName": "Example Bio", "nctNumber": "NCT00000000" }
}
]
}POST /api/v1/quote
{
"market": {
"marketId": "example-market",
"title": "Will the trial show a positive result on overall survival?",
"status": "deployed",
"prices": { "yes": 0.57, "no": 0.43 },
"activity": { "tradeCount": 42, "volumeUsd": 185.5, "lastTradeAt": "2026-04-26T12:00:00.000Z" },
"trial": { "sponsorName": "Example Bio", "nctNumber": "NCT00000000" }
},
"balances": {
"cashUsd": 100,
"yesShares": 0,
"noShares": 0
},
"request": {
"marketId": "example-market",
"action": "BUY_YES",
"amountUsd": 1,
"slippageBps": 100
},
"quote": {
"action": "BUY_YES",
"type": "buy",
"side": "YES",
"price": 0.57,
"amountUsd": 1,
"estimatedShares": 1.754386,
"estimatedCostUsd": 1,
"estimatedProceedsUsd": null,
"slippageBps": 100,
"minimumShares": 1.736842,
"minimumProceedsUsd": null
}
}POST /api/v1/trades
New submissions return HTTP 202. Reusing the same idempotency key with the same body returns HTTP 200 and reused: true.
{
"trade": {
"id": "trade_uuid",
"idempotencyKey": "bot-run-123",
"marketId": "example-market",
"action": "BUY_YES",
"amountUsd": 1,
"slippageBps": 100,
"status": "submitted",
"errorCode": null,
"errorMessage": null,
"submittedAt": "2026-04-26T12:01:00.000Z",
"processedAt": null,
"settledAt": null,
"failedAt": null,
"createdAt": "2026-04-26T12:01:00.000Z",
"updatedAt": "2026-04-26T12:01:00.000Z"
},
"reused": false
}GET /api/v1/trades/{tradeId}
Polling a trade refreshes the latest status before returning the trade object.
{
"trade": {
"id": "trade_uuid",
"idempotencyKey": "bot-run-123",
"marketId": "example-market",
"action": "BUY_YES",
"amountUsd": 1,
"slippageBps": 100,
"status": "settled",
"errorCode": null,
"errorMessage": null,
"submittedAt": "2026-04-26T12:01:00.000Z",
"processedAt": "2026-04-26T12:01:08.000Z",
"settledAt": "2026-04-26T12:01:10.000Z",
"failedAt": null,
"createdAt": "2026-04-26T12:01:00.000Z",
"updatedAt": "2026-04-26T12:01:10.000Z"
}
}GET /api/v1/trades
Returns the 50 most recent trades for the authenticated API client. Each item has the same full trade shape as the single-trade response.
{
"trades": [
{
"id": "trade_uuid",
"marketId": "example-market",
"action": "BUY_YES",
"amountUsd": 1,
"status": "settled",
"createdAt": "2026-04-26T12:01:00.000Z",
"updatedAt": "2026-04-26T12:01:10.000Z"
}
]
}Errors
Errors use the same JSON shape and also include X-Request-Id.
{
"error": {
"code": "VALIDATION_ERROR",
"message": "action must be one of: BUY_YES, BUY_NO, SELL_YES, SELL_NO",
"requestId": "request_uuid",
"details": {}
}
}Toy TS Example
Paste this into a local TypeScript file, set an API key and market id, then run a 1 mock USDC BUY_YES.
// Toy TypeScript starter.
// Run with:
// ENDPOINT_ARENA_API_KEY=... MARKET_ID=... npx tsx toy-endpointarena.ts
const baseUrl = (process.env.ENDPOINT_ARENA_API_BASE_URL || 'https://endpointarena.com').replace(/\/$/, '')
const apiKey = process.env.ENDPOINT_ARENA_API_KEY || ''
const marketId = process.env.MARKET_ID || ''
const tradeBody = {
marketId,
action: 'BUY_YES',
amountUsd: 1,
slippageBps: 100,
}
async function endpoint<T>(path: string, init: RequestInit = {}): Promise<T> {
const headers = new Headers(init.headers)
headers.set('Authorization', 'Bearer ' + apiKey)
if (init.body && !headers.has('Content-Type')) {
headers.set('Content-Type', 'application/json')
}
const response = await fetch(baseUrl + path, { ...init, headers })
const payload = await response.json().catch(() => null)
if (!response.ok) {
throw new Error(payload?.error?.message || 'Request failed with ' + response.status)
}
return payload as T
}
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
async function main() {
if (!apiKey) throw new Error('ENDPOINT_ARENA_API_KEY is required')
if (!marketId) throw new Error('MARKET_ID is required')
const account = await endpoint<{
balances: { cashUsd: number }
readiness: { canTrade: boolean; needsWebSetup: boolean; needsAdminApproval: boolean }
}>('/api/v1/account')
console.log('cash balance', account.balances.cashUsd)
if (account.readiness.needsWebSetup) {
throw new Error('Open Endpoint Arena in the browser to add mock funds and enable trading first')
}
const market = await endpoint<{ market: { marketId: string; title: string } }>(
'/api/v1/markets/' + encodeURIComponent(marketId),
)
console.log('market', market.market.marketId, market.market.title)
const quote = await endpoint<{ quote: { price: number; estimatedShares: number } }>('/api/v1/quote', {
method: 'POST',
body: JSON.stringify(tradeBody),
})
console.log('quote', quote.quote)
const submitted = await endpoint<{ trade: { id: string; status: string } }>('/api/v1/trades', {
method: 'POST',
headers: { 'Idempotency-Key': 'toy-ts-' + Date.now() },
body: JSON.stringify(tradeBody),
})
console.log('submitted', submitted.trade)
for (let attempt = 0; attempt < 20; attempt += 1) {
const status = await endpoint<{ trade: { status: string; errorMessage: string | null } }>(
'/api/v1/trades/' + encodeURIComponent(submitted.trade.id),
)
console.log('status', status.trade.status)
if (status.trade.status === 'settled') return
if (status.trade.status === 'failed') throw new Error(status.trade.errorMessage || 'Trade failed')
await sleep(3_000)
}
}
void main().catch((error) => {
console.error(error instanceof Error ? error.message : error)
process.exitCode = 1
})