Agent trading operator guide¶
An agent trades for one cabal with nothing but its key. It never holds the cabal's money. It sends buy and sell intents to Monaco. Monaco checks each one against the agent's budget and executes it from the cabal treasury, through the same Jupiter swap and Privy treasury path as a member vote. Fills stay in the cabal treasury.
The stocks are xStocks on Solana, named by symbol, such as AAPLx.
The backend is being rewritten (backend-platform.md). This page describes the target contract. Until the cutover, the current backend still answers with the older shapes in legacy/api.md.
Setup¶
- A member proposes add agent with a name and a USDC budget.
- The cabal votes. When it passes, Monaco mints a key:
monaco_ak_followed by 32 random characters (about 158 bits fromcrypto/rand). - Any member opens Group → Agent and taps Copy connect instructions. The text holds the key, the API base URL and a link to
/v1/agent/skill.md. Paste it into the agent. Copy key copies the key alone.
The key is readable by cabal members until the agent is removed. Revoking the agent wipes it. Never log the key.
API base URL¶
The API base is PUBLIC_API_BASE_URL. Local dev uses http://127.0.0.1:8080. A deployed API sets it to its public https URL, and the connect instructions carry the same value.
Auth¶
Send the key on every call:
X-Monaco-Agent-Key: monaco_ak_k7m2p9x4…
The key alone names the agent and its cabal. No cabal id and no member login are needed. A missing, unknown or revoked key gets 401.
The backend reads X-Monaco-Agent-Key beside the bearer auth that app users send. A valid key produces an agent actor in the request context (Context rules), so every command and event records that the agent acted, not a member. A request carries one or the other; agent routes accept only the key.
Only wrong keys are counted, 10 per cabal and 10 per caller address, refilling one a minute:
- An address that has used its 10 gets 429 with
Retry-Afteron every call, whatever key it sends. An agent only lands here by sending wrong keys itself. - Wrong keys aimed at your cabal from elsewhere never lock out your agent. A call with a
monaco_ak_…key is always checked, and passes if the key is right.
The caller address is the TCP peer, or the left-most X-Forwarded-For entry when the API runs with TRUST_PROXY_HEADERS=true.
Routes¶
| Route | Key | Purpose |
|---|---|---|
GET /v1/agent |
yes | Cabal, agent, status, budget, cash and the agent's own holdings |
GET /v1/agent/assets |
yes | The stocks the agent may trade, with a current price for each |
POST /v1/agent/intents |
yes | Buy or sell |
GET /v1/agent/intents/{intentId} |
yes | Where one intent ended up |
GET /v1/agent/skill.md |
no | Instructions written for an LLM agent |
Money comes back two ways. Decimal strings ("10.50", "0.25000000") are for people and LLMs. Integer atomic fields (usdc_micros, shares_atomic) are for code. USDC has 6 decimals. xStocks have 8.
Read the agent¶
curl -sS -H "X-Monaco-Agent-Key: $MONACO_AGENT_KEY" "$MONACO_API/v1/agent"
{
"cabal_name": "Tech Bros",
"agent_name": "Scout",
"status": "active",
"budget": {
"allocation_usd": "100.00", "allocation_usdc_micros": 100000000,
"available_usd": "42.10", "available_usdc_micros": 42100000
},
"cash_available_usd": "42.10", "cash_available_usdc_micros": 42100000,
"holdings": [
{"symbol": "AAPLx", "name": "Apple xStock", "shares": "0.25000000", "shares_atomic": 25000000,
"mark_usd": "230.12", "mark_usdc_micros": 230120000, "value_usd": "57.53", "value_usdc_micros": 57530000}
],
"limits": {"intents_per_hour": 30, "reason_max_chars": 280},
"docs_url": "http://127.0.0.1:8080/v1/agent/skill.md"
}
statusisactiveorpaused.cash_available_usdis what the agent can spend right now. It is the smaller of its available budget and the treasury's USDC.holdingslists only what the agent itself bought and still holds. The mark fields are null when Monaco has no price for that stock right now.
List assets¶
curl -sS -H "X-Monaco-Agent-Key: $MONACO_AGENT_KEY" "$MONACO_API/v1/agent/assets?limit=25"
{"assets": [{"symbol": "AAPLx", "name": "Apple xStock", "solana_mint": "Xs…", "routable": true,
"mark_usd": "230.12", "mark_usdc_micros": 230120000}], "has_more": false}
Optional query parameters are query, limit (max 100) and offset. Only routable stocks are listed. mark_usd is null for a stock whose price is unavailable, and the list still returns 200.
Use these symbols and prices. Do not call Jupiter, xStocks or Solana from the agent.
Post an intent¶
A buy spends USD. Send exactly one of usd (a decimal string, up to 6 decimals) or usdc_micros (an integer, USD × 10⁶).
curl -sS -X POST \
-H "X-Monaco-Agent-Key: $MONACO_AGENT_KEY" \
-H "Idempotency-Key: 3f1c…" \
-H "Content-Type: application/json" \
"$MONACO_API/v1/agent/intents" \
-d '{"side":"buy","symbol":"AAPLx","usd":"10.50","reason":"breakout above 20d high"}'
A sell names shares. Send exactly one of shares (a decimal string, up to 8 decimals) or token_amount (an integer, shares × 10⁸).
curl -sS -X POST \
-H "X-Monaco-Agent-Key: $MONACO_AGENT_KEY" \
-H "Idempotency-Key: 9a2e…" \
-H "Content-Type: application/json" \
"$MONACO_API/v1/agent/intents" \
-d '{"side":"sell","symbol":"AAPLx","shares":"0.25","reason":"took profit"}'
Both forms at once, neither form, or the other side's field is a 422.
reason is optional, up to 280 characters. Monaco stores it with the intent and shows it to the cabal. Say briefly why the agent made the trade.
Success returns the intent_id with "status": "accepted", before the swap settles. The swap then shows as pending and then confirmed in cabal activity.
scripts/demo/agent-intent.sh buy AAPLx 1 sends one intent from the terminal. See its --help.
Read an intent¶
curl -sS -H "X-Monaco-Agent-Key: $MONACO_AGENT_KEY" "$MONACO_API/v1/agent/intents/$INTENT_ID"
{"intent_id": "…", "side": "buy", "symbol": "AAPLx", "status": "executed",
"reject_reason": null, "reason": "breakout",
"usdc_micros": 10500000, "token_amount": null,
"transaction_id": "…", "tx_signature": "5x…", "filled_token_amount": 4560000, "filled_usdc_micros": 10500000,
"created_at": "2026-09-24T12:00:00Z"}
status is accepted while the swap is in flight, then executed, rejected or failed. Another agent's intent and an unknown id are both 404.
Idempotency and retries¶
Send an Idempotency-Key header on every intent, unique per trade decision. The server stores the first response and replays it for a resend with the same key, so a retry never trades twice. An intent goes through the async trade engine, and the response returns before the swap settles. Read GET /v1/agent/intents/{intentId} for the outcome. See Thin client and Flows.
Rate limits¶
Each key may send 30 intents an hour. Reads (GET /v1/agent*) allow a burst of 120 and refill one every 30 seconds. Over either limit, Monaco answers 429 with Retry-After in seconds. Wait that long, then continue. A resend under the same Idempotency-Key still counts as a request.
Errors¶
Every error body is RFC 9457 application/problem+json with a stable code, a user-facing message, a trace_id, and retryable. Branch on code, not on the message text. Quote trace_id when reporting a problem. See Errors.
Budget¶
The add-agent vote sets the agent's USDC budget. Available budget is:
allocation − executed buys − in-flight buys + proceeds of the agent's confirmed sells
It never goes below zero. Sells refill the budget. An agent with a $100 budget that buys $100 of stock and later sells it for $110 has $110 to spend again. The treasury's USDC is still the hard ceiling.
One agent's intents are decided one at a time, and a buy reserves its amount before the swap is sent. Parallel intents cannot overshoot the budget.
Sell only what you bought¶
An agent may sell only what its own confirmed buys returned, less what it has already sold or is selling. Stocks the cabal bought by vote are never the agent's to sell, even in the same treasury. Selling those takes a sell proposal. If a member-voted sell took part of what the agent bought, the agent is limited to what the treasury still holds. holdings in GET /v1/agent shows exactly what the agent may sell.
Pause, resume, revoke¶
Each is a cabal vote. A paused agent keeps its key, and its intents get 403. A revoked agent's key gets 401. Resume restores the same key.
Security¶
Keep the key in a secret store. To rotate it, revoke the agent and vote in a new one.
Log¶
- 2026-09-29: Agent auth is the
X-Monaco-Agent-Keyheader beside bearer auth, producing anagentactor (default; see #535). JSON fields are snake_case, per the RFC's Decided. Removed Definitive Flash, five-character keys and the momentum bot example.