3rd party API
Overview
HTTP API for external partners to manage users, wallets, trading, and AI conversations on Milo. All endpoints are versioned under /api/v1 and require an API key (except signup).
Need an API key? Contact us on Discord
Authentication
All endpoints (except signup) require the X-API-Key header:
curl -H "X-API-Key: mk_live_..." https://partners.andmilo.com/api/v1/users/{userId}/positionsUser-scoped endpoints use {userId} in the path. Wallet-scoped endpoints use {walletId}.
MCP Endpoint
The same server also exposes an MCP Streamable HTTP endpoint at:
POST /mcp(initialize + JSON-RPC requests)GET /mcp(SSE stream for a session)DELETE /mcp(terminate a session)
Notes:
Use
Mcp-Session-Idfor sessioned requests after initialization.Acceptshould includeapplication/jsonandtext/event-streamfor POST.POST /mcpinitialize accepts optionalX-API-Key.If omitted, the session starts unauthenticated and only signup/public MCP tools are usable until
signupreturns an API key.MCP tools that forward paginated endpoints enforce
page <= 100andpageSize <= 100.MCP forwards caller IP to downstream partner-api calls so signup/SIWX per-IP throttling remains per caller.
MCP sessions are bounded and idle sessions are evicted; initialize may return
429under saturation.If a session expires, sessioned calls return invalid-session errors and the client should re-initialize.
There is no server-side API-key fallback for MCP sessions.
MCP
create_conversationandsend_messagesurface conversation-write overage (402 Payment Required) as structured payment guidance (paymentSupport) and support optionalpaymentinput to attach proof on retry.Overage payment options are
0.25 USDCor0.01 SOLtoTREASURY_WALLET(recipient address is returned inpaymentSupport.recipient/X-Payment-Recipient).Retry using MCP
create_conversationorsend_messagewith:payment.recipient,payment.asset,payment.amount,payment.paymentId,payment.txSignatureoptional
payment.header(defaultX-PAYMENT)
Client setup examples (OpenAI Codex + Claude Desktop): see docs/mcp.md.
Quick Start
Sign up — Request a SIWX message, sign it with your wallet, register
Save credentials — Store
apiKey,user.id, and wallet IDsDeposit SOL — Send SOL to your Milo wallet address
Activate auto-trading —
PATCH /api/v1/users/{userId}/auto-trade-settingswith{ "isActive": true }Fetch open quests —
GET /api/v1/users/{userId}/queststo see available quests. Claim bones for completed quests.
Endpoints
Me
Get Current User
GET /api/v1/me
Resolve the authenticated API key to the user profile and wallets. Use this to discover your userId and walletId values.
Response:
Signup
Signup is public (no API key needed) and has two steps.
Step 1: Get SIWX Message
POST /api/v1/users/siwx/message
accountAddress
string
yes
Solana wallet address (32-64 chars)
chainId
string
yes
CAIP-2 chain ID
inviteCode
string
no
Optional invite code
Response:
Sign the message string (UTF-8 bytes) with your ed25519 private key. Base58-encode the signature.
Step 2: Register
POST /api/v1/users
Pass data and message exactly as returned by step 1.
Response:
You get two wallets:
signup — Your external signing wallet
milo — Your trading wallet. Deposit SOL here.
Returns 409 Conflict if the wallet already belongs to an existing user.
Auto-Trade Settings
Get Settings
GET /api/v1/users/{userId}/auto-trade-settings
Returns the current configuration including strategySync status if a strategy is linked.
Update Settings
PATCH /api/v1/users/{userId}/auto-trade-settings
isActive
boolean
Enable/disable auto-trading
riskTolerance
string
conservative, balanced, degen
strategy
string
VALUE INVESTOR, SWING TRADER, SCALPER, CUSTOM
strategyId
uuid | null
Link a saved strategy
instructions
string
Free-text trading instructions
customTickers
string[]
Tokens to focus on
allocation
object
Asset class percentages
Asset classes: trenches, memes, promising-memes, staking, native, majors, stables, xStocks, custom
isActive can only be set to true if the Milo wallet holds at least 1 SOL.
Strategies
Reusable autotrade configuration presets. Create a strategy, link it to your settings, and sync when it changes.
Create Strategy
POST /api/v1/users/{userId}/auto-trade-settings/strategies
name
string
yes
Strategy name (1-200 chars)
strategy
string
yes
VALUE INVESTOR, SWING TRADER, SCALPER, CUSTOM
description
string
no
Description (max 2000 chars)
instructions
string
no
Free-text instructions (max 4000 chars)
allocation
object
no
Asset class percentages
customTickers
string[]
no
Token tickers to focus on
isPublic
boolean
no
Make publicly discoverable
List Strategies
GET /api/v1/users/{userId}/auto-trade-settings/strategies
scope
string
all, owned, public
q
string
Search by name/description
page
number
Page number (default: 1)
pageSize
number
Items per page (default: 25, max: 100)
Get Strategy
GET /api/v1/users/{userId}/auto-trade-settings/strategies/{strategyId}
Update Strategy
PATCH /api/v1/users/{userId}/auto-trade-settings/strategies/{strategyId}
All fields from create are optional.
Delete Strategy
DELETE /api/v1/users/{userId}/auto-trade-settings/strategies/{strategyId}
Sync Strategy
POST /api/v1/users/{userId}/auto-trade-settings/strategies/{strategyId}/sync
Re-applies the latest strategy snapshot to your auto-trade settings. Use this when strategySync.synced is false in the GET settings response.
Strategy workflow:
Create a strategy
Link it:
PATCH /auto-trade-settingswith{ "strategyId": "..." }Milo trades using the snapshot
If the strategy is updated, GET settings shows
strategySync.synced: falseCall sync to re-apply the latest version
Arena
Deploy a public strategy to the arena leaderboard. Milo creates a custody wallet, funds it, and trades autonomously using the strategy. The strategy must be public and owned by the user. Deployment requires at least 1 SOL balance (0.01 SOL in dev). Withdrawing transfers all holdings back to the user's Milo wallet.
Deploy to Arena
POST /api/v1/users/{userId}/arena/deploy
strategyId
uuid
yes
ID of a public strategy owned by the user
Response:
Withdraw from Arena
POST /api/v1/users/{userId}/arena/withdraw
strategyId
uuid
yes
ID of the deployed strategy to withdraw
Response:
Get Arena Leaderboard
GET /api/v1/users/{userId}/arena/leaderboard
timeframe
string
1d, 30d, 90d
—
page
number
Page number
1
pageSize
number
Items per page (max: 100)
25
sortKey
string
pnl, winRate, returnPct, accountValue
—
sortDirection
string
asc, desc
—
Response:
Quests & Bones
Quests are event-driven tasks that reward bones (points) upon completion. Each quest has requirements (count, sum, or streak-based). Agents should check quests regularly — fetch open quests and use unclaimed=true to find completed quests to claim.
List Quests
GET /api/v1/users/{userId}/quests
By default returns only unlocked (available) quests.
unlocked
boolean
Filter for unlocked quests (available)
true
unclaimed
boolean
Filter for completed but unclaimed quests
—
claimed
boolean
Filter for claimed quests
—
mode
string
completed_last
—
page
number
Page number
1
pageSize
number
Items per page (max: 100)
25
Response:
Claim Quest
POST /api/v1/users/{userId}/quests/{questId}/claim
Returns { "data": null } on success, 404 if not found or already claimed.
Get Bones Balance
GET /api/v1/users/{userId}/quests/bones
Response:
Orders
Create Order
POST /api/v1/wallets/{walletId}/orders
Amount types:
absolute
amount
Raw token amount
absolute_usd
amount
USD equivalent
relative
percentage
Percentage of position (sell only)
Trigger types:
absolute
gte, lte
Trigger at absolute price
relative
rise, drop
Trigger on % change from entry
Market order: { "type": "absolute", "trigger": "price", "operator": "gte", "value": 0 }
Take-profit / Stop-loss (optional):
takeProfits array items:
percentage(1-100) — Percent of position to sellprofitPercentage(> 0) — Profit % to trigger
stopLosses array items:
percentage(1-100) — Percent of position to selllossPercentage(1-100) — Loss % to trigger
Dependant creation flow:
Main order is created first.
TP and SL dependants are created sequentially as draft sell children (
parentId= main order ID).Dependant failures are returned per dependant item while the main order still returns
201 Created.
Guardrails (always enforce mode):
takeProfits.length <= 5stopLosses.length <= 5takeProfits.length + stopLosses.length <= 8
Guardrail violations return 400 bad_request with a clear validation message.
Response:
List Orders
GET /api/v1/users/{userId}/orders
status
string
active, paused, error, fulfilled, archived, draft
type
string
buy, sell
tokenAddress
string
Filter by token
page
number
Page number (default: 1, max: 100)
pageSize
number
Items per page (default: 25, max: 100)
Get Order
GET /api/v1/users/{userId}/orders/{orderId}
Pause Order
POST /api/v1/users/{userId}/orders/{orderId}/pause
Activate Order
POST /api/v1/users/{userId}/orders/{orderId}/activate
Delete Order
DELETE /api/v1/users/{userId}/orders/{orderId}
Wallet Actions
Send Tokens
POST /api/v1/wallets/{walletId}/actions/send
recipient
string
Destination Solana address
token
string
Token mint address
amount
number
Amount in human-readable units (e.g. 1.5 SOL)
For native SOL use mint: So11111111111111111111111111111111111111112
Response (202):
Conversations
Milo uses async conversations. Send a message, then poll for the response.
Create Conversation
POST /api/v1/users/{userId}/conversations
message
string
yes
Initial message (1-4000 chars)
agentType
string
no
Agent type (default: market-analyst)
Agent types:
Market Analyst
market-analyst
Token research, technicals, sentiment
Auto Trader
auto-trader
Strategy discussion, can update settings
Response (201):
List Conversations
GET /api/v1/users/{userId}/conversations
Supports page and pageSize query params.
Get Conversation
GET /api/v1/users/{userId}/conversations/{conversationId}
Send Message
POST /api/v1/users/{userId}/conversations/{conversationId}/messages
Conversation Overage Payments
Conversation write endpoints (create + send message) include 2 free writes per 60s per API key (shared across conversations).
When over the free quota, the API returns 402 Payment Required.
Payment header:
X-PAYMENTRecipient wallet:
TREASURY_WALLET(server environment variable)Accepted overage prices:
0.25 USDC0.01 SOL
Anti-replay: each paid overage request must include a unique one-time
paymentId(reusing it is rejected).On-chain verification: each paid request must include
txSignaturefor a confirmed Solana transfer toTREASURY_WALLETwith the matching asset+amount.
402 example response:
Paid retry example:
Get Messages
GET /api/v1/users/{userId}/conversations/{conversationId}/messages
Response:
Polling pattern:
Send a message (POST)
Poll GET messages every 2-3 seconds
When
processingisfalse, the agent has finished
Positions
List Positions
GET /api/v1/users/{userId}/positions
status
active, pending, not_active
page
Page number (default: 1, max: 100)
pageSize
Items per page (default: 25, max: 100)
Each position includes entry/exit orders, TP/SL orders, invested amount, realized/unrealized PnL, and current value.
Close Position
POST /api/v1/users/{userId}/positions/{thesisId}/close
Cancels pending orders and creates a sell order for remaining holdings.
Close All Positions
POST /api/v1/users/{userId}/positions/close-all
Closes all active and pending positions. Partial failures don't block other positions.
Response:
Holdings
Get Holdings
GET /api/v1/wallets/{walletId}/holdings
Response:
Transactions
Get Transactions
GET /api/v1/wallets/{walletId}/transactions
All on-chain transactions for a wallet. Uses cursor-based pagination.
limit
number
Items per page (default: 25, max: 200)
cursor
string
Cursor from previous response
Response:
Get Executed Transactions
GET /api/v1/wallets/{walletId}/executed-transactions
Only transactions linked to orders (trades). Uses cursor-based pagination.
limit
number
Items per page (default: 25, max: 200)
cursor
string
Cursor from previous response
txType
string
Filter: buy or sell
token
string
Filter by token address
Diary Logs
GET /api/v1/users/{userId}/diary-logs
Auto-trade diary entries showing what Milo did and why.
Response:
Pagination
Most list endpoints use page-based pagination:
page
1
100
pageSize
25
100
Response includes:
Transactions and executed transactions use cursor-based pagination with limit and cursor params, returning a nextCursor field.
Rate Limits
Signup (per IP)
5
60s
Signup (per wallet)
1
60s
SIWX message (per IP)
5
60s
Me (GET /api/v1/me)
60
60s
Portfolio reads (holdings, transactions, positions, diary-logs)
60
60s
Auto-trade settings (write)
10
60s
Strategies (write)
10
60s
Strategies (read)
60
60s
Conversations (write: create, send message)
2 free then paid overage
60s
Conversations (read)
30
60s
Arena write (deploy, withdraw)
5
60s
Arena read (leaderboard)
30
60s
Quests read (list, bones balance)
60
60s
Quests write (claim)
10
60s
Wallet actions
10
60s
Orders create (POST /wallets/{walletId}/orders)
5
60s
Orders write (pause, activate, delete)
10
60s
Orders (read)
60
60s
Position close
10
60s
Position close-all (POST /users/{userId}/positions/close-all)
1
60s
Rate limit rejections return 429 Too Many Requests with:
error.code = "rate_limit_exceeded"Retry-Afterheader (seconds)X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Resetheaders
Retry behavior: wait for Retry-After before retrying.
Conversation write overage returns 402 Payment Required with:
error.code = "payment_required"X-Payment-Required: trueX-Payment-Header: X-PAYMENTX-Payment-Recipient: <TREASURY_WALLET>X-Payment-Options: USDC:0.25,SOL:0.01X-Payment-Id-Field: paymentId(one-time value required per paid request)X-Payment-Tx-Field: txSignature(confirmed payment transaction signature)
Successful paid overage retries (2xx) include:
PAYMENT-RESPONSE: <base64-json-settlement>(x402-style acceptance payload)X-PAYMENT-RESPONSE: <same-value>(legacy mirror)X-Billing-Mode: payg
Error Handling
400
bad_request
Invalid input or validation error
401
unauthorized
Missing or invalid API key
402
payment_required
Conversation write overage requires payment
404
not_found
Resource not found
409
error
Conflict (e.g. wallet already registered)
429
rate_limit_exceeded
Rate limit exceeded
500
internal_error
Server error
Last updated