ワンクリックで
milo
Autonomous Solana portfolio management. Non-custodial wallets, auto-trading, market analysis, orders, transfers, and AI conversations.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Autonomous Solana portfolio management. Non-custodial wallets, auto-trading, market analysis, orders, transfers, and AI conversations.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
| name | milo |
| version | 1.0.0 |
| description | Autonomous Solana portfolio management. Non-custodial wallets, auto-trading, market analysis, orders, transfers, and AI conversations. |
Milo is an autonomous Solana portfolio manager. Through this API you can register users, create non-custodial wallets, send tokens, place buy/sell orders, manage positions, configure auto-trading strategies, and converse with Milo's AI agents.
Follow these 4 steps to go from zero to a fully active Milo account.
Request a SIWX message, sign it with your Solana wallet, and register.
# 1a. Get the message to sign
curl -X POST {{BASE_URL}}/api/v1/users/siwx/message \
-H "Content-Type: application/json" \
-d '{
"accountAddress": "<your-wallet-address>",
"chainId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
}'
# 1b. Sign the returned "message" (UTF-8 bytes) with your ed25519 key, base58-encode the signature
# 1c. Register
curl -X POST {{BASE_URL}}/api/v1/users \
-H "Content-Type: application/json" \
-d '{
"signupWallet": "<your-wallet-address>",
"siwx": {
"data": <data-object-from-1a>,
"message": "<message-string-from-1a>",
"signature": "<base58-signature-from-1b>"
}
}'
# → returns apiKey, user.id, wallets[] (save these to ~/.milo/config.json)
Start a conversation with the market analyst to explore current opportunities.
# 2a. Create a conversation
curl -X POST {{BASE_URL}}/api/v1/users/{userId}/conversations \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": "What tokens are trending right now on Solana?",
"agentType": "market-analyst"
}'
# → returns conversationId
# 2b. Poll for the response (wait 2-3s between polls)
curl "{{BASE_URL}}/api/v1/users/{userId}/conversations/{conversationId}/messages" \
-H "X-API-Key: $API_KEY"
# → when processing: false, the agent has responded
Deposit SOL to your Milo wallet so it has capital to trade. The Milo wallet address is wallets[1].address from signup (the type: "milo" wallet).
Send SOL from your external wallet to that address. Once funded, verify:
curl {{BASE_URL}}/api/v1/wallets/{walletId}/holdings \
-H "X-API-Key: $API_KEY"
Turn on Milo's autonomous trading agent. It will start scanning for opportunities within ~30 minutes.
curl -X PATCH {{BASE_URL}}/api/v1/users/{userId}/auto-trade-settings \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"isActive": true,
"riskTolerance": "balanced",
"strategy": "SWING TRADER"
}'
That's it — Milo is now managing your portfolio. Read on for the full feature guide and API reference.
Note: You do NOT need to read this entire document. Use the guide below to jump to the relevant section for your task.
| If you need to... | Read section |
|---|---|
| Look up your user ID and wallet IDs | "Me" (under Complete API Reference) |
| Register a new user / get an API key | "First Boot Protocol" and "Signup" (under Complete API Reference) |
| Activate or configure auto-trading | "Recipe 2: Activate the auto-trader" and "Auto-Trade Settings" (under Complete API Reference) |
| Create, link, or manage strategies | "Strategies" (under Complete API Reference) |
| Deploy and manage arena strategies | "Arena" (under Complete API Reference) |
| View quests and claim bones | "Quests & Bones" (under Complete API Reference) |
| Place, list, or manage orders | "Orders" (under Complete API Reference) |
| Send tokens to another wallet | "Wallet Actions" (under Complete API Reference) |
| Check positions, holdings, or PnL | "Recipe 4: Fetch your positions" and "Portfolio" (under Complete API Reference) |
| Chat with Milo's AI agents | "Recipe 3: Talk with Milo" / "Recipe 6: Ask about a token" and "Conversations" (under Complete API Reference) |
| Understand the full onboarding flow | "Quick Start" and "First Boot Protocol" |
| Set up a recurring portfolio check-in | "Heartbeat Protocol" |
Capabilities:
POST /api/v1/users/siwx/message with your wallet address to get a message to sign.POST /api/v1/users with the signed SIWX proof to create a user and receive an API key + Milo wallet.~/.milo/config.json.type: "milo" wallet from signup).PATCH /api/v1/users/{userId}/auto-trade-settings with { "isActive": true }.GET /api/v1/users/{userId}/quests to see available quests. Claim bones for any completed quests.After signup, persist credentials locally so they survive across sessions:
// ~/.milo/config.json
{
"api_key": "mk_live_...",
"user_id": "uuid",
"wallet_id": "uuid",
"wallet_address": "7xKXtg...",
"base_url": "{{BASE_URL}}"
}
Warning: In ephemeral environments (containers, serverless), this file may not persist. Use environment variables or a secrets manager instead.
All authenticated requests require the header:
X-API-Key: <api_key>
(NOT Authorization: Bearer — the API uses the X-API-Key header.)
The partner API server exposes MCP on the same host at /mcp using Streamable HTTP:
POST /mcp for initialize and JSON-RPC requestsGET /mcp for SSE streamDELETE /mcp for session terminationPOST /mcp initialize accepts optional X-API-Key. If omitted, the MCP session starts unauthenticated and only signup/public tools are usable until signup returns an API key. Request throttling and session-capacity protections are enforced, so initialize may return 429. On 429, respect Retry-After and retry after the indicated delay. Sessions can also expire, so re-initialize after invalid-session errors.
For sessioned requests after initialize, send Mcp-Session-Id.
Milo creates a non-custodial Solana wallet for each user via Turnkey. The wallet-creating account is the owner; Milo receives delegated permission for trading.
On signup you receive two wallets:
type: "signup" - Your external signing wallet (used for SIWX verification).type: "milo" - Your Milo trading wallet. Deposit SOL here for trading.To check your wallet balance, use the holdings endpoint:
curl {{BASE_URL}}/api/v1/wallets/{walletId}/holdings \
-H "X-API-Key: $API_KEY"
When starting fresh with no saved credentials:
curl -X POST {{BASE_URL}}/api/v1/users/siwx/message \
-H "Content-Type: application/json" \
-d '{
"accountAddress": "<your-wallet-address>",
"chainId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
}'
This returns { "data": { ... }, "message": "<message-string>" }. The data object contains all SIWX fields (domain, uri, nonce, etc.) and the message is the human-readable string to sign.message string (UTF-8 encoded bytes) with your Solana keypair's ed25519 private key. Base58-encode the resulting signature.curl -X POST {{BASE_URL}}/api/v1/users \
-H "Content-Type: application/json" \
-d '{
"signupWallet": "<your-wallet-address>",
"siwx": {
"data": <data-object-from-step-2>,
"message": "<message-string-from-step-2>",
"signature": "<base58-signature-from-step-3>"
}
}'
Important: Pass the data and message exactly as returned by the server. Do not construct them manually.apiKey, user.id, and wallets[1] (the Milo wallet) to ~/.milo/config.json.curl -X PATCH {{BASE_URL}}/api/v1/users/{userId}/auto-trade-settings \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"isActive": true,
"riskTolerance": "balanced",
"strategy": "SWING TRADER"
}'
See heartbeat.md for the recurring check-in protocol. Run every 4+ hours to stay informed about your portfolio.
This cookbook walks through the most common workflows end-to-end. Follow these recipes to get up and running quickly.
Before anything else you need credentials. Generate a Solana keypair, request a SIWX message from the server, sign it, and register.
# 1. Request a SIWX message to sign
curl -X POST {{BASE_URL}}/api/v1/users/siwx/message \
-H "Content-Type: application/json" \
-d '{
"accountAddress": "<your-wallet-address>",
"chainId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
}'
# → returns { "data": { "data": { ... }, "message": "<message-to-sign>" } }
# 2. Sign the "message" string (UTF-8 bytes) with your ed25519 private key
# Base58-encode the signature
# 3. Register with the signed proof
curl -X POST {{BASE_URL}}/api/v1/users \
-H "Content-Type: application/json" \
-d '{
"signupWallet": "<your-wallet-address>",
"siwx": {
"data": <data-object-from-step-1>,
"message": "<message-string-from-step-1>",
"signature": "<base58-signature-from-step-2>"
}
}'
# 4. Save the response — you need these for every subsequent call
# api_key → Authorization header
# user.id → {userId} in routes
# wallets[1].id → {walletId} (the "milo" wallet)
# wallets[1].address → deposit SOL here
Store credentials in ~/.milo/config.json so they persist across sessions.
Once registered, turn on Milo's autonomous trading agent.
curl -X PATCH {{BASE_URL}}/api/v1/users/{userId}/auto-trade-settings \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"isActive": true,
"riskTolerance": "balanced",
"strategy": "SWING TRADER",
"instructions": "Focus on SOL ecosystem tokens. Avoid meme coins.",
"customTickers": ["SOL", "JUP", "BONK"]
}'
That's it — Milo will start monitoring markets and placing trades according to your configuration. You can verify it's active:
curl {{BASE_URL}}/api/v1/users/{userId}/auto-trade-settings \
-H "X-API-Key: $API_KEY"
Use the auto-trader agent to discuss, refine, or brainstorm your trading strategy. This is a conversation — Milo understands your portfolio context.
# 1. Start a conversation with the auto-trader
curl -X POST {{BASE_URL}}/api/v1/users/{userId}/conversations \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": "I want to shift my strategy toward DeFi blue-chips. What allocation do you recommend for a balanced risk profile?",
"agentType": "auto-trader"
}'
# → returns { conversationId, processing: true }
# 2. Poll for the response (wait 2-3s between polls)
curl "{{BASE_URL}}/api/v1/users/{userId}/conversations/{conversationId}/messages" \
-H "X-API-Key: $API_KEY"
# → when processing: false, the agent has responded
# 3. Continue the conversation
curl -X POST {{BASE_URL}}/api/v1/users/{userId}/conversations/{conversationId}/messages \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "message": "Good plan. Apply those changes to my auto-trade settings." }'
Tip: The auto-trader agent can directly update your settings when you ask it to — it has tool access to modify your allocation, risk tolerance, and instructions.
Check what Milo is holding and how each position is performing.
# List all active positions with PnL
curl "{{BASE_URL}}/api/v1/users/{userId}/positions?status=active&page=1&pageSize=50" \
-H "X-API-Key: $API_KEY"
Each position includes the token, entry price, current PnL, and thesis. Use status=pending for positions still being built, or omit the filter to get everything.
For a deeper look at your wallet's token balances:
curl {{BASE_URL}}/api/v1/wallets/{walletId}/holdings \
-H "X-API-Key: $API_KEY"
Have Milo re-analyze an existing position to decide whether to hold, add, or exit.
# 1. Get the position's thesisId from the positions list (Recipe 4)
# 2. Start a conversation with the auto-trader, referencing the position
curl -X POST {{BASE_URL}}/api/v1/users/{userId}/conversations \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": "Re-evaluate my position on JUP (thesis {thesisId}). Given the latest market conditions, should I hold, take partial profit, or close it entirely?",
"agentType": "auto-trader"
}'
# 3. Poll for the response
curl "{{BASE_URL}}/api/v1/users/{userId}/conversations/{conversationId}/messages" \
-H "X-API-Key: $API_KEY"
# 4. If Milo recommends closing, you can close it directly:
curl -X POST {{BASE_URL}}/api/v1/users/{userId}/positions/{thesisId}/close \
-H "X-API-Key: $API_KEY"
Use the market-analyst agent for token research. It can pull market data, analyze trends, and give you a thesis.
# 1. Ask the market analyst about a token
curl -X POST {{BASE_URL}}/api/v1/users/{userId}/conversations \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": "What do you think about RENDER? Give me a full analysis — fundamentals, technicals, and whether it fits my current portfolio.",
"agentType": "market-analyst"
}'
# 2. Poll for the response
curl "{{BASE_URL}}/api/v1/users/{userId}/conversations/{conversationId}/messages" \
-H "X-API-Key: $API_KEY"
# 3. Follow up with more questions in the same conversation
curl -X POST {{BASE_URL}}/api/v1/users/{userId}/conversations/{conversationId}/messages \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "message": "Compare it with RNDR vs HNT for a DePIN play. Which is the better entry right now?" }'
Tip: The market-analyst agent focuses on research and analysis. If you want Milo to actually execute a trade based on the analysis, either create an order manually or switch to the auto-trader agent.
A typical session with Milo looks like:
Run the heartbeat protocol every 4+ hours to keep this cycle going automatically.
All endpoints (except signup) require:
X-API-Key: <api_key>
Get the current user profile and wallets for the authenticated API key. Use this to discover your userId and walletId values.
curl {{BASE_URL}}/api/v1/me \
-H "X-API-Key: $API_KEY"
Response (200):
{
"user": {
"id": "uuid",
"signupWalletId": "uuid",
"provider": "siwx",
"createdAt": "2025-01-01T00:00:00.000Z"
},
"wallets": [
{ "id": "uuid", "address": "<address>", "chain": "solana", "type": "signup" },
{ "id": "uuid", "address": "<address>", "chain": "solana", "type": "milo" }
]
}
Signup is a two-step process: first request a SIWX message from the server, then sign it and submit the proof.
Generate a SIWX message for the wallet to sign. This is step 1 of signup.
curl -X POST {{BASE_URL}}/api/v1/users/siwx/message \
-H "Content-Type: application/json" \
-d '{
"accountAddress": "<wallet-address>",
"chainId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
}'
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
accountAddress | string | yes | Solana wallet address (32-64 chars) |
chainId | string | yes | CAIP-2 chain ID (e.g. solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp) |
inviteCode | string | no | Optional invite code |
Response (200):
{
"data": {
"data": {
"accountAddress": "<wallet-address>",
"chainId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
"domain": "andmilo.com",
"uri": "https://andmilo.com",
"version": "v1",
"nonce": "<server-generated-nonce>",
"issuedAt": "2025-06-15T12:00:00.000Z",
"expirationTime": "2025-06-22T12:00:00.000Z",
"statement": "By signing, you agree to andmilo Terms of Use..."
},
"message": "andmilo.com wants you to sign in with your Solana account:\n<wallet-address>\n\n..."
}
}
Sign the message string (UTF-8 encoded bytes) with the wallet's ed25519 private key. Base58-encode the signature. Then proceed to step 2.
Create a new user via SIWX wallet verification. This is step 2 of signup.
curl -X POST {{BASE_URL}}/api/v1/users \
-H "Content-Type: application/json" \
-d '{
"signupWallet": "<wallet-address>",
"siwx": {
"data": <data-object-from-siwx-message-response>,
"message": "<message-string-from-siwx-message-response>",
"signature": "<base58-ed25519-signature>"
}
}'
Important: Pass the data and message fields exactly as returned by POST /api/v1/users/siwx/message. Do not construct them manually.
Response (200):
{
"data": {
"user": { "id": "uuid", "signupWalletId": "uuid", "provider": "siwx", "createdAt": "..." },
"wallets": [
{ "id": "uuid", "address": "<address>", "chain": "solana", "type": "signup" },
{ "id": "uuid", "address": "<address>", "chain": "solana", "type": "milo" }
],
"apiKey": "mk_live_..."
}
}
Get current auto-trade configuration.
curl {{BASE_URL}}/api/v1/users/{userId}/auto-trade-settings \
-H "X-API-Key: $API_KEY"
Update auto-trade configuration.
curl -X PATCH {{BASE_URL}}/api/v1/users/{userId}/auto-trade-settings \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"isActive": true,
"riskTolerance": "balanced",
"strategy": "SWING TRADER",
"instructions": "Focus on SOL ecosystem tokens",
"customTickers": ["SOL", "JUP", "BONK"],
"dataSources": { "fundingRates": true, "openInterest": true },
"assetClassSettings": {
"memes": { "dataSources": { "liquidationData": true } },
"majors": { "dataSources": { "macroData": true } }
}
}'
Settings fields:
| Field | Type | Values |
|---|---|---|
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 to auto-trade settings |
modelVersion | string | null | Preferred model |
instructions | string | Free-text trading instructions for the agent |
customTickers | string[] | Specific tokens to focus on |
allocation | object | Asset class allocation percentages |
dataSources | object | null | Global data-source toggles: fundingRates, openInterest, liquidationData, macroData |
assetClassSettings | object | null | Per-asset-class configuration, including nested dataSources overrides |
Asset classes for allocation: trenches, memes, promising-memes, staking, native, majors, stables, xStocks, custom
Data-source resolution notes:
dataSources overrides win over top-level dataSources.dataSources both globally and inside assetClassSettings, so partial updates do not wipe sibling keys.Model entitlement notes:
o3, gpt-5.2-high, gpt-5.2-xh, and gpt-5.4.claude-opus-4.5 and claude-opus-4.6.gemini-3-pro and gemini-3.1-pro-preview.grok-4.1-fast-reasoning and grok-4.400 Bad Request with error.details.requiredPlan, error.details.upgradeUrl, and an error message containing the same plan-specific Stripe link.Data-source entitlement notes:
pro and max users can create, update, apply, sync, or otherwise change dataSources.dataSources in GET responses.dataSources remain visible but are inactive in auto-trader chat, execution, and position review until the account is back on Pro or Max.400 Bad Request with error.details.feature = "dataSources", error.details.requiredPlan = "pro", and upgrade details when available.Strategies are reusable autotrade configurations that live under auto-trade settings. Create a strategy, link it to your settings, and sync when the strategy evolves.
Workflow:
PATCH /auto-trade-settings with { "strategyId": "<uuid>" }. This takes a snapshot of the strategy into your settings.strategySync.synced: false.POST .../strategies/{strategyId}/sync to re-apply the latest version.Create a new strategy.
curl -X POST {{BASE_URL}}/api/v1/users/{userId}/auto-trade-settings/strategies \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "SOL Ecosystem DCA",
"description": "Dollar cost average into SOL ecosystem tokens",
"strategy": "SWING TRADER",
"instructions": "Focus on SOL, JUP, and BONK with balanced entries",
"allocation": { "majors": 45, "native": 25, "staking": 10, "promising-memes": 15, "xStocks": 5 },
"customTickers": ["SOL", "JUP", "BONK"],
"dataSources": { "fundingRates": true, "openInterest": true },
"assetClassSettings": {
"memes": { "dataSources": { "liquidationData": true } },
"majors": { "dataSources": { "macroData": true } }
},
"isPublic": false
}'
Response (201):
{
"data": {
"id": "uuid",
"name": "SOL Ecosystem DCA",
"strategy": "SWING TRADER",
"allocation": { ... },
"createdAt": "..."
}
}
Create/update fields:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes (create) | Strategy name (1-200 chars) |
description | string | null | no | Description (max 2000 chars) |
instructions | string | no | Free-text instructions (max 4000 chars) |
strategy | string | yes (create) | VALUE INVESTOR, SWING TRADER, SCALPER, CUSTOM |
allocation | object | no | Asset class allocation percentages |
customTickers | string[] | null | no | Token tickers to focus on |
dataSources | object | null | no | Global data-source toggles carried into the strategy snapshot |
assetClassSettings | object | null | no | Per-asset-class configuration, including nested dataSources |
isPublic | boolean | no | Make strategy publicly discoverable |
The same Pro/Max restriction applies when creating or updating a strategy with dataSources, and when syncing a linked strategy that already contains them.
List strategies with filters.
curl "{{BASE_URL}}/api/v1/users/{userId}/auto-trade-settings/strategies?scope=owned&page=1&pageSize=25" \
-H "X-API-Key: $API_KEY"
Query parameters:
| Param | Values | Description |
|---|---|---|
scope | all, owned, public | Filter by ownership |
q | string | Search by name/description |
page | number | Page number (default: 1) |
pageSize | number | Items per page (default: 25, max: 100) |
Get strategy details.
curl {{BASE_URL}}/api/v1/users/{userId}/auto-trade-settings/strategies/{strategyId} \
-H "X-API-Key: $API_KEY"
Update a strategy. All fields are optional.
curl -X PATCH {{BASE_URL}}/api/v1/users/{userId}/auto-trade-settings/strategies/{strategyId} \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Updated Strategy Name",
"instructions": "New instructions for the agent"
}'
Delete a strategy.
curl -X DELETE {{BASE_URL}}/api/v1/users/{userId}/auto-trade-settings/strategies/{strategyId} \
-H "X-API-Key: $API_KEY"
Re-sync auto-trade settings with the linked strategy. Takes a fresh snapshot of the strategy's current allocation, instructions, and configuration.
curl -X POST {{BASE_URL}}/api/v1/users/{userId}/auto-trade-settings/strategies/{strategyId}/sync \
-H "X-API-Key: $API_KEY"
Strategy sync workflow:
curl -X PATCH {{BASE_URL}}/api/v1/users/{userId}/auto-trade-settings \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "strategyId": "<strategy-uuid>", "isActive": true }'
{
"strategyId": "uuid",
"strategySync": {
"strategyId": "uuid",
"synced": false,
"strategyUpdatedAt": "2025-06-15T14:00:00.000Z",
"settingsUpdatedAt": "2025-06-01T10:00:00.000Z"
}
}
synced is false, re-sync:curl -X POST {{BASE_URL}}/api/v1/users/{userId}/auto-trade-settings/strategies/{strategyId}/sync \
-H "X-API-Key: $API_KEY"
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 a strategy to the arena.
curl -X POST {{BASE_URL}}/api/v1/users/{userId}/arena/deploy \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "strategyId": "<strategy-uuid>" }'
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
strategyId | uuid | yes | ID of a public strategy owned by the user |
Response (200):
{
"data": {
"arenaUserId": "uuid",
"custodyWalletId": "uuid",
"custodyWalletAddress": "<solana-address>",
"fundingTxSignature": "<transaction-signature>",
"strategyId": "uuid"
}
}
Withdraw from the arena. Transfers all holdings from the custody wallet back to the user's Milo wallet.
curl -X POST {{BASE_URL}}/api/v1/users/{userId}/arena/withdraw \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "strategyId": "<strategy-uuid>" }'
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
strategyId | uuid | yes | ID of the deployed strategy to withdraw |
Response (200):
{
"data": {
"arenaUserId": "uuid",
"custodyWalletId": "uuid",
"custodyWalletAddress": "<solana-address>",
"recipientWalletAddress": "<solana-address>",
"transferTxs": [
{ "tokenAddress": "<mint-address>", "amount": 1.5, "signature": "<tx-signature>" }
]
}
}
Get the arena leaderboard.
curl "{{BASE_URL}}/api/v1/users/{userId}/arena/leaderboard?timeframe=30d&page=1&pageSize=25&sortKey=pnl&sortDirection=desc" \
-H "X-API-Key: $API_KEY"
Query parameters:
| Param | Type | Values | Default |
|---|---|---|---|
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 | — |
winRate is token-PnL based: (number of tokens with positive token PnL / total tracked tokens) * 100, excluding USDC.
Response (200):
{
"data": [
{
"strategy": "uuid",
"strategyName": "SOL Ecosystem DCA",
"ownerUserId": "uuid",
"ownerUsername": "trader1",
"pnl": 120.50,
"winRate": 0.65,
"returnPct": 12.5,
"accountValue": 1120.50,
"arenaWalletAddress": "<solana-address>",
"currentHoldings": [ ... ]
}
],
"meta": { "page": 1, "pageSize": 25, "total": 100, "pages": 4 }
}
Quests are event-driven tasks (trades, signups, etc.) that reward bones (points) upon completion. Each quest has requirements (count, sum, or streak-based) and awards bones when all requirements are met. Check quests regularly — call list_quests to see open quests, and with unclaimed=true to discover completed quests, then claim_quest to collect the bones.
List quests with progress and requirements. By default returns only unlocked (available) quests.
curl "{{BASE_URL}}/api/v1/users/{userId}/quests?page=1&pageSize=25" \
-H "X-API-Key: $API_KEY"
Query parameters:
| Param | Type | Values | Default |
|---|---|---|---|
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 (200):
{
"data": [
{
"userId": "uuid",
"questId": "uuid",
"title": "First Trade",
"description": "Execute your first trade",
"award": 100,
"totalRequirements": 1,
"completedRequirements": 1,
"claimed": false,
"completed": true,
"unlocked": true,
"requirements": [
{
"requirementId": "uuid",
"aggregationKind": "count",
"targetValue": 1,
"currentValue": 1,
"completed": true
}
]
}
],
"meta": { "page": 1, "pageSize": 25, "total": 10, "pages": 1 }
}
Claim bones for a completed quest.
curl -X POST {{BASE_URL}}/api/v1/users/{userId}/quests/{questId}/claim \
-H "X-API-Key: $API_KEY"
Response (200):
{ "data": null }
Returns 404 if the quest is not found or already claimed.
Get the user's bones balance.
curl "{{BASE_URL}}/api/v1/users/{userId}/quests/bones" \
-H "X-API-Key: $API_KEY"
Response (200):
{
"data": {
"userId": "uuid",
"username": "alice",
"balance": 500,
"unclaimed": 100
}
}
balance — total bones already claimedunclaimed — bones from completed quests not yet claimed (call claim_quest to collect)Send tokens from your Milo wallet to a recipient address.
curl -X POST {{BASE_URL}}/api/v1/wallets/{walletId}/actions/send \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"recipient": "<solana-address>",
"token": "<token-mint-address>",
"amount": 1.5
}'
Response (202):
{ "data": "<transaction-signature>" }
Note: For native SOL, use the SOL mint address:
So11111111111111111111111111111111111111112.Funding wallet: the transfer is balance-checked against, and funded from, the
{walletId}in the path. If you hold several Milo wallets, each send draws only on the wallet you name — no other wallet of yours is debited. If a JSON body includeswalletId, it must match the{walletId}path parameter.
Create a new order, optionally with take-profit and stop-loss dependants.
curl -X POST {{BASE_URL}}/api/v1/wallets/{walletId}/orders \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"tokenAddress": "So11111111111111111111111111111111111111112",
"type": "buy",
"status": "active",
"expiresAt": "2026-12-31T23:59:59.000Z",
"payload": {
"type": "buy",
"amount": { "type": "absolute_usd", "amount": 50 },
"trigger": { "type": "absolute", "trigger": "price", "operator": "gte", "value": 0 },
"execution": {}
},
"takeProfits": [
{ "percentage": 50, "profitPercentage": 20 },
{ "percentage": 50, "profitPercentage": 50 }
],
"stopLosses": [
{ "percentage": 100, "lossPercentage": 15 }
]
}'
Response includes the main order plus a dependants array:
{
"data": {
"id": "...",
"type": "buy",
"status": "active",
"dependants": [
{ "type": "take_profit", "order": { "id": "...", "parentId": "...", "subType": "take_profit", "status": "draft" } },
{ "type": "take_profit", "order": { "id": "...", "parentId": "...", "subType": "take_profit", "status": "draft" } },
{ "type": "stop_loss", "order": { "id": "...", "parentId": "...", "subType": "stop_loss", "status": "draft" } }
]
}
}
Take-profit / Stop-loss ladder (optional):
takeProfits array — each item:
percentage (1–100) — percent of position to sellprofitPercentage (> 0) — profit percent to trigger the TPstopLosses array — each item:
percentage (1–100) — percent of position to selllossPercentage (1–100) — loss percent to trigger the SLDependant creation flow (enforced):
parentId = main order ID).201.Guardrails (always enforce mode):
takeProfits.length <= 5stopLosses.length <= 5takeProfits.length + stopLosses.length <= 8Violations return 400 bad_request with a clear validation message. Dependants use relative triggers (rise for TP, drop for SL).
Order payload structure:
Buy order amounts:
{ "type": "absolute", "amount": 1000000 } - Raw token amount{ "type": "absolute_usd", "amount": 50 } - USD equivalentSell order amounts (additional option):
{ "type": "relative", "percentage": 50 } - Percentage of positionTrigger types:
{ "type": "absolute", "trigger": "price", "operator": "gte", "value": 0 }{ "type": "absolute", "trigger": "price", "operator": "lte", "value": 150.00 }{ "type": "relative", "trigger": "price", "operator": "drop", "value": 10 } (10% drop){ "type": "relative", "trigger": "price", "operator": "rise", "value": 25 } (25% rise)Market orders require expiresAt, and it must be within 120 minutes of the request time.
Execution options (all optional):
slippagePercentage - Max slippage (0-100)priorityFee - Priority fee in SOL lamportsplatformFeeBps - Platform fee override in basis pointsLinking to a thesis: Pass positionThesisId (UUID) in the body to attach the order (and its TP/SL dependants) to a position thesis.
List orders with filters.
curl "{{BASE_URL}}/api/v1/users/{userId}/orders?status=active&type=buy&page=1&pageSize=25" \
-H "X-API-Key: $API_KEY"
Query parameters:
| Param | Type | Values |
|---|---|---|
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 details.
curl {{BASE_URL}}/api/v1/users/{userId}/orders/{orderId} \
-H "X-API-Key: $API_KEY"
Pause an active order.
curl -X POST {{BASE_URL}}/api/v1/users/{userId}/orders/{orderId}/pause \
-H "X-API-Key: $API_KEY"
Activate a draft or paused order.
curl -X POST {{BASE_URL}}/api/v1/users/{userId}/orders/{orderId}/activate \
-H "X-API-Key: $API_KEY"
Archive (delete) an order.
curl -X DELETE {{BASE_URL}}/api/v1/users/{userId}/orders/{orderId} \
-H "X-API-Key: $API_KEY"
Milo uses an async conversation model. You send a message and the agent processes it in the background. Poll the messages endpoint for the response using the processing flag.
Create a new conversation and send the first message.
curl -X POST {{BASE_URL}}/api/v1/users/{userId}/conversations \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": "What is the current market sentiment for SOL?",
"agentType": "market-analyst"
}'
Agent types:
| Agent | ID | Purpose |
|---|---|---|
| Market Analyst | market-analyst | Market analysis, token research, sentiment |
| Auto Trader | auto-trader | Trading strategy discussion |
| Game Agent | milo-game-agent | Gamified trading experience |
Response (201):
{
"data": {
"conversationId": "...",
"status": "active",
"agentActive": true,
"processing": true,
"createdAt": "..."
}
}
List conversations.
curl "{{BASE_URL}}/api/v1/users/{userId}/conversations?page=1&pageSize=25" \
-H "X-API-Key: $API_KEY"
Get conversation details.
curl {{BASE_URL}}/api/v1/users/{userId}/conversations/{conversationId} \
-H "X-API-Key: $API_KEY"
Send a follow-up message.
curl -X POST {{BASE_URL}}/api/v1/users/{userId}/conversations/{conversationId}/messages \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "message": "What about JUP?" }'
Poll for messages. Check processing flag to know if the agent is still working.
curl "{{BASE_URL}}/api/v1/users/{userId}/conversations/{conversationId}/messages?page=1&pageSize=25" \
-H "X-API-Key: $API_KEY"
Response:
{
"data": {
"messages": [
{ "messageId": "...", "role": "user", "content": "...", "createdAt": "..." },
{ "messageId": "...", "role": "assistant", "content": "...", "createdAt": "..." }
],
"processing": false
},
"meta": { "page": 1, "pageSize": 25, "total": 2, "pages": 1 }
}
Polling pattern:
processing is false, the agent has finished responding.Get current token holdings for a wallet.
curl {{BASE_URL}}/api/v1/wallets/{walletId}/holdings \
-H "X-API-Key: $API_KEY"
Get all transactions for a wallet.
curl "{{BASE_URL}}/api/v1/wallets/{walletId}/transactions?page=1&pageSize=25" \
-H "X-API-Key: $API_KEY"
Get executed transactions (order-linked trades) for a wallet. Uses cursor-based pagination.
curl "{{BASE_URL}}/api/v1/wallets/{walletId}/executed-transactions?limit=25" \
-H "X-API-Key: $API_KEY"
# To paginate, pass the nextCursor from the previous response:
curl "{{BASE_URL}}/api/v1/wallets/{walletId}/executed-transactions?limit=25&cursor=<nextCursor>" \
-H "X-API-Key: $API_KEY"
Query parameters:
| Param | Type | Description |
|---|---|---|
limit | number | Items per page (default: 25, max: 200) |
cursor | string | Cursor from previous response's nextCursor |
txType | string | Filter by buy or sell |
token | string | Filter by token address |
Get positions with PnL data.
curl "{{BASE_URL}}/api/v1/users/{userId}/positions?status=active&page=1&pageSize=25" \
-H "X-API-Key: $API_KEY"
Query parameters:
| Param | Values |
|---|---|
status | active, pending, not_active |
Close a single position. Cancels pending orders and creates a sell order for remaining holdings.
curl -X POST {{BASE_URL}}/api/v1/users/{userId}/positions/{thesisId}/close \
-H "X-API-Key: $API_KEY"
Close all active and pending positions for a user. Each position is closed independently — partial failures don't block other positions.
curl -X POST {{BASE_URL}}/api/v1/users/{userId}/positions/close-all \
-H "X-API-Key: $API_KEY"
Response:
{
"data": {
"successes": [
{ "thesisId": "...", "cancelled": 2, "sellOrderCreated": true }
],
"failures": [
{ "thesisId": "...", "error": "No wallet found" }
]
}
}
Get auto-trade diary logs.
curl "{{BASE_URL}}/api/v1/users/{userId}/diary-logs?page=1&pageSize=25" \
-H "X-API-Key: $API_KEY"
Rate limits are enforced across endpoints. Effective thresholds vary by endpoint and request context and may be adjusted over time.
Rate limit rejections return 429 Too Many Requests with:
error.code = "rate_limit_exceeded"Retry-After header (seconds)X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset headersRetry behavior: back off and retry only after Retry-After elapses.
All list endpoints support pagination:
page - Page number (default: 1, max: 100)pageSize - Items per page (default: 25, max: 100)Response includes meta:
{ "meta": { "page": 1, "pageSize": 25, "total": 100, "pages": 4 } }
| Status | Code | Description |
|---|---|---|
| 400 | bad_request | Invalid input, missing fields, validation error |
| 401 | unauthorized | Missing or invalid API key |
| 404 | not_found | Resource not found |
| 409 | error | Conflict (e.g., user already exists) |
| 429 | rate_limit_exceeded | Rate limit exceeded |
| 500 | internal_error | Unexpected server error |
Error response format:
{
"error": {
"code": "bad_request",
"message": "Model \"gpt-5.2-high\" requires a Pro plan. Upgrade here: https://buy.stripe.com/...",
"details": {
"requestedModelVersion": "gpt-5.2-high",
"requiredPlan": "pro",
"requiredPlanLabel": "Pro",
"upgradeUrl": "https://buy.stripe.com/...",
"upgradeText": "Upgrade to Pro to use model \"gpt-5.2-high\"."
}
}
}
Schema validation failures also return 400 bad_request, with error.details.validationIssues as a field-level issue list:
{
"error": {
"code": "bad_request",
"message": "Missing required field: expiresAt",
"details": {
"validationIssues": [
{
"target": "json",
"path": "expiresAt",
"code": "invalid_type",
"message": "Required"
}
]
}
}
}