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 Discordarrow-up-right

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}/positions

User-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-Id for sessioned requests after initialization.

  • Accept should include application/json and text/event-stream for POST.

  • POST /mcp initialize accepts optional X-API-Key.

  • If omitted, the session starts unauthenticated and only signup/public MCP tools are usable until signup returns an API key.

  • MCP tools that forward paginated endpoints enforce page <= 100 and pageSize <= 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 429 under 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_conversation and send_message surface conversation-write overage (402 Payment Required) as structured payment guidance (paymentSupport) and support optional payment input to attach proof on retry.

  • Overage payment options are 0.25 USDC or 0.01 SOL to TREASURY_WALLET (recipient address is returned in paymentSupport.recipient / X-Payment-Recipient).

  • Retry using MCP create_conversation or send_message with:

    • payment.recipient, payment.asset, payment.amount, payment.paymentId, payment.txSignature

    • optional payment.header (default X-PAYMENT)

Client setup examples (OpenAI Codex + Claude Desktop): see docs/mcp.md.

Quick Start

  1. Sign up — Request a SIWX message, sign it with your wallet, register

  2. Save credentials — Store apiKey, user.id, and wallet IDs

  3. Deposit SOL — Send SOL to your Milo wallet address

  4. Activate auto-tradingPATCH /api/v1/users/{userId}/auto-trade-settings with { "isActive": true }

  5. Fetch open questsGET /api/v1/users/{userId}/quests to 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

Field
Type
Required
Description

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

Field
Type
Description

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

Field
Type
Required
Description

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

Param
Type
Description

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:

  1. Create a strategy

  2. Link it: PATCH /auto-trade-settings with { "strategyId": "..." }

  3. Milo trades using the snapshot

  4. If the strategy is updated, GET settings shows strategySync.synced: false

  5. Call 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

Field
Type
Required
Description

strategyId

uuid

yes

ID of a public strategy owned by the user

Response:

Withdraw from Arena

POST /api/v1/users/{userId}/arena/withdraw

Field
Type
Required
Description

strategyId

uuid

yes

ID of the deployed strategy to withdraw

Response:

Get Arena Leaderboard

GET /api/v1/users/{userId}/arena/leaderboard

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

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.

Param
Type
Description
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:

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:

Type
Fields
Description

absolute

amount

Raw token amount

absolute_usd

amount

USD equivalent

relative

percentage

Percentage of position (sell only)

Trigger types:

Type
Operator
Description

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 sell

  • profitPercentage (> 0) — Profit % to trigger

stopLosses array items:

  • percentage (1-100) — Percent of position to sell

  • lossPercentage (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 <= 5

  • stopLosses.length <= 5

  • takeProfits.length + stopLosses.length <= 8

Guardrail violations return 400 bad_request with a clear validation message.

Response:

List Orders

GET /api/v1/users/{userId}/orders

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

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

Field
Type
Description

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

Field
Type
Required
Description

message

string

yes

Initial message (1-4000 chars)

agentType

string

no

Agent type (default: market-analyst)

Agent types:

Agent
Value
Purpose

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-PAYMENT

  • Recipient wallet: TREASURY_WALLET (server environment variable)

  • Accepted overage prices:

    • 0.25 USDC

    • 0.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 txSignature for a confirmed Solana transfer to TREASURY_WALLET with the matching asset+amount.

402 example response:

Paid retry example:

Get Messages

GET /api/v1/users/{userId}/conversations/{conversationId}/messages

Response:

Polling pattern:

  1. Send a message (POST)

  2. Poll GET messages every 2-3 seconds

  3. When processing is false, the agent has finished


Positions

List Positions

GET /api/v1/users/{userId}/positions

Param
Values

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.

Param
Type
Description

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.

Param
Type
Description

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:

Param
Default
Max

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

Endpoint Group
Limit
Window

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-After header (seconds)

  • X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset headers

Retry behavior: wait for Retry-After before retrying.

Conversation write overage returns 402 Payment Required with:

  • error.code = "payment_required"

  • X-Payment-Required: true

  • X-Payment-Header: X-PAYMENT

  • X-Payment-Recipient: <TREASURY_WALLET>

  • X-Payment-Options: USDC:0.25,SOL:0.01

  • X-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

Status
Code
Description

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