← Back to docs
INTEGRATION
x402 AGENT SPEC
Battle Trade accepts x402-wrapped trading agents natively. Drop in an x402 envelope from the EasyA hackathon and it goes straight into Bot Arena — same engine, same ranked matches, same on-chain Trader Score.
# x402 Agent Spec (Battle Trade)
**Status:** Pre-1.0. Accepted for the EasyA hackathon cohort at Consensus
Miami 2026. Will converge with the upstream x402 protocol once the spec
lands. Until then, treat this doc as the source of truth for what
battle.fyi will accept.
## TL;DR
Upload JSON to `POST /api/agents/upload`. The endpoint needs auth (see
[Authentication](#authentication) below). Your body can be either:
1. **Native** — Battle Trade's own agent config:
```json
{ "config": <AgentConfig> }
```
2. **x402 envelope** — wrap the same config in a protocol-aware shell:
```json
{
"x402": {
"x402_version": "0.1",
"agent": {
"name": "MomentumBull",
"description": "Long BTC when 5m change > 1%",
"config": <AgentConfig>
},
"metadata": { "author_wallet": "0x...", "license": "MIT" }
}
}
```
The server derives `profile_id` from your authenticated session, so do
NOT include it in the body. Either form above ends up in the same
`agents` table. Envelope metadata is preserved under
`config.x402_metadata` for audit and leaderboard display.
## Authentication
`POST /api/agents/upload` requires an authenticated caller. Three ways
to send that identity:
| Method | Header | Best for |
|---|---|---|
| Privy session cookie | `Cookie: bt_session_token=...` (browser auto-sends) | Uploading via the website after logging in |
| Privy JWT | `Authorization: Bearer <privy_jwt>` | Programmatic access from agent tooling or CI |
| Guest ID | `X-Guest-Id: <profile_id>` | Testing; limited to guest-tier profiles |
There is no unauthenticated path. Omitting all three returns `401
{ "error": "Authentication required" }`. The easiest flow for
hackathon devs: log into battle.fyi in your browser, open DevTools →
Application → Cookies, copy the `bt_session_token` value, and send it
with your curl. It persists for 7 days.
## AgentConfig schema
```ts
{
name: string; // 2-40 chars
description: string; // up to 200 chars
version: number; // integer, you bump on each publish
species?: string; // creature avatar: bull|bear|shark|hawk|wolf|fox|snake|dragon|turtle|octopus|phoenix|owl
rules: AgentRule[]; // 1-20 rules, evaluated top-to-bottom each tick
risk: {
max_positions: number; // 1-10
max_leverage: number; // 1-50
stop_loss_pct: number; // -50 to 0
take_profit_pct: number; // 0 to 100
};
}
```
### Rule grammar
```ts
{
condition: string; // e.g. "price_change_5m > threshold"
action: "open_long" | "open_short" | "close_all" | "close_longs" | "close_shorts" | "reduce_position";
asset?: "BTC" | "ETH" | "SOL" | ...; // see VALID_ASSETS
size?: "small" | "medium" | "large" | "max";
leverage?: number; // capped by risk.max_leverage
}
```
Built-in condition tokens:
- `price_change_1m > threshold` (and `5m`, `15m`, `<`)
- `unrealized_pnl_pct > threshold` / `< threshold`
- `position_count > threshold` / `< threshold`
Custom conditions are accepted as opaque strings — the executor will
ignore unknown tokens rather than crash.
## x402 envelope fields
| Field | Required | Notes |
|--------------------|----------|------------------------------------------|
| `x402_version` | yes | Semver string. We currently accept "0.x".|
| `agent.name` | no | Falls back to `config.name`. |
| `agent.description`| no | Falls back to `config.description`. |
| `agent.config` | yes | Native `AgentConfig` (see above). |
| `metadata` | no | Free-form. Preserved verbatim in DB. |
## Example
```json
{
"x402": {
"x402_version": "0.1",
"agent": {
"name": "BreakoutHunter",
"description": "5m momentum longs, tight stop",
"config": {
"name": "BreakoutHunter",
"description": "5m momentum longs, tight stop",
"version": 1,
"species": "hawk",
"rules": [
{ "condition": "price_change_5m > threshold", "action": "open_long", "asset": "BTC", "size": "medium", "leverage": 5 },
{ "condition": "unrealized_pnl_pct < threshold", "action": "close_all" }
],
"risk": {
"max_positions": 3,
"max_leverage": 10,
"stop_loss_pct": -5,
"take_profit_pct": 15
}
}
},
"metadata": {
"author_wallet": "0xabcd...",
"x402_payment_ref": "0xpayment..."
}
}
}
```
`profile_id` is derived from your auth (not from the body).
## Limits
- Max 10 agents per profile
- 1-20 rules per agent
- Leverage capped at 50x (further capped per-lobby)
- Position count capped at 10
## Gate codes (Bot Arena access)
Uploading an agent is unrestricted for any authenticated profile.
Entering a live **Bot Arena** lobby is gated during Miami launch week.
Valid codes:
| Code | Who it's for |
|---|---|
| `MIAMI2026` | Booth walk-ups during Consensus Miami |
| `HACKATHON_DEVS` | EasyA hackathon cohort (you) |
| `PUBLIC_LAUNCH` | Post-April-28 public access (also auto-accepted when `BOT_ARENA_PUBLIC=true` env flag is on) |
Enter the code at `battle.fyi/agents` before deploying your agent to a
lobby. If your code starts failing, check the pinned message in the
`#base-miami-trading-comp` Telegram thread for rotation.
## Upload reference
### Request (with Privy JWT)
```bash
curl -X POST https://battle.fyi/api/agents/upload \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $PRIVY_JWT" \
-d @your-agent.json
```
### Request (with session cookie — for browser-logged-in devs)
```bash
# After logging in at battle.fyi, copy bt_session_token from DevTools:
curl -X POST https://battle.fyi/api/agents/upload \
-H "Content-Type: application/json" \
-H "Cookie: bt_session_token=$SESSION_COOKIE" \
-d @your-agent.json
```
### Request (with guest ID)
```bash
curl -X POST https://battle.fyi/api/agents/upload \
-H "Content-Type: application/json" \
-H "X-Guest-Id: $YOUR_GUEST_PROFILE_ID" \
-d @your-agent.json
```
### Response — success (201)
```json
{
"agent": {
"id": "agt_...",
"profile_id": "prof_...",
"name": "BreakoutHunter",
"description": "5m momentum longs, tight stop",
"config": { /* normalized AgentConfig with x402_metadata preserved */ },
"version": 1,
"status": "draft",
"wins": 0,
"losses": 0,
"total_battles": 0,
"created_at": "2026-05-05T14:30:00Z"
}
}
```
### Response — validation failure (422)
```json
{
"error": "Invalid agent config",
"details": [
"name too long (max 40 characters)",
"rules[0].asset \"UNICORN\" not supported (allowed: BTC, ETH, SOL, ...)",
"risk.max_leverage must be 1-50"
]
}
```
### Response — request too large (413)
```json
{ "error": "Request too large (max 102400 bytes)" }
```
### Response — rate limited (429)
```json
{ "error": "Too many uploads, slow down" }
```
Includes `Retry-After: 60` header. Limit is 10 uploads / minute / IP.
### Response — malformed JSON (400)
```json
{ "error": "Invalid JSON body" }
```
### Response — not authenticated (401)
```json
{ "error": "Authentication required" }
```
Send one of the three auth methods above.
## Body size + rate limits
| Limit | Value |
|-------|-------|
| Max body size | 100 KB |
| Uploads per IP per minute | 10 |
| Max agents per profile | 10 |
| Max `x402_metadata` size | 5 KB (JSON-stringified) |
| Max `rules[].condition` length | 200 characters |
| Max `name` length | 40 characters |
| Max `description` length | 200 characters |
## Competing in Bot Arena
Once uploaded, your agent is in `draft` status. To enter a Bot Arena lobby:
1. Visit `https://battle.fyi/agents` (need `HACKATHON_DEVS` gate code)
2. Pick your agent → deploy → enter a Bot Arena lobby
3. Your bot trades automatically for the round duration
4. Scores show on the live leaderboard at `/lobby/[id]/wall`
Agents are paper-only — no real capital at stake. Scored by final PnL %.
## Questions
Battle Trade side: DM @ellisnorman_ on TG.
Upstream x402 spec: follow the x402 protocol docs.