# Luckylobster

> Trade prediction markets on Polymarket. Search markets, place orders, and manage positions.

- Skill: `dvcrn/luckylobster-2` (Agent Skill)
- Install (CLI): `npx skillmds@latest add dvcrn/luckylobster-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/dvcrn/luckylobster-2/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: dvcrn (https://skillmd.com/u/dvcrn)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/dvcrn/luckylobster-2

---


# LuckyLobster - Polymarket Trading API

Trade prediction markets on Polymarket through a secure API designed for AI agents.

## How Polymarket Works

Polymarket is a prediction market where you trade on the outcomes of real-world events.

**Buying Contracts:**
- In active markets, you can buy outcome contracts priced from $0.01 to $0.99
- Each contract entitles you to 1 share at your purchase price
- Lower prices = higher potential return, but less likely outcome (market's view)

**Selling Contracts:**
- You can sell your shares at any time before the market closes
- Sell price depends on current market conditions

**Market Resolution:**
- When a market resolves, the winning outcome pays **$1.00 USDC per share**
- Losing outcomes pay $0 (or a negligible amount in rare cases)

**Example:** You buy 100 "Yes" shares at $0.35 each (cost: $35). If "Yes" wins, you receive $100 (profit: $65). If "No" wins, you lose your $35.

## Setup

If you don't have an API key configured, use the device authorization flow to link your account.

### Device Authorization Flow

**Step 1: Request a Device Code**

```http
POST https://luckylobster.io/api/auth/device
Content-Type: application/json

{
  "agent_name": "OpenClaw Agent"
}
```

Response:
```json
{
  "device_code": "abc123...",
  "user_code": "ABCD-1234",
  "verification_uri": "https://luckylobster.io/link",
  "verification_uri_complete": "https://luckylobster.io/link?code=ABCD-1234",
  "expires_in": 900,
  "interval": 5
}
```

**Step 2: Direct the User**

Parse the JSON response and extract the `verification_uri_complete` field. Display it to the user as a clickable link:

```
🦞 To connect LuckyLobster, click: {verification_uri_complete}
```

**Important:** Use the `verification_uri_complete` value exactly as returned — do NOT concatenate fields or build the URL yourself. The value is a complete, ready-to-use URL.

**Step 3: Poll for Authorization**

Poll every 5 seconds until authorized:

```http
GET https://luckylobster.io/api/auth/device/token?device_code=abc123...
```

Pending response:
```json
{ "error": "authorization_pending" }
```

Success response:
```json
{
  "api_key": "ll_abc123...",
  "user_email": "user@example.com",
  "permissions": ["read", "trade", "cancel", "redeem"]
}
```

All linked agents receive standard permissions: **read** (view markets/orders/positions), **trade** (buy/sell), **cancel** (cancel orders), and **redeem** (settle positions).

**Step 4: Store the API Key**

Save the API key persistently so it survives restarts. It is only returned once.

Use the `gateway` tool with `config.patch` to save it in the skill config:

```javascript
gateway.config.patch({
  patch: {
    skills: {
      entries: {
        luckylobster: {
          env: {
            LUCKYLOBSTER_API_KEY: "ll_abc123..."
          }
        }
      }
    }
  }
})
```

---

## Authentication

All API requests require an API key in the Authorization header:

```
Authorization: Bearer YOUR_API_KEY
```

## Base URL

```
https://luckylobster.io/api/agent/v1
```

## Rate Limits

- Default: 100 requests per minute
- Rate limit headers included in responses:
  - `X-RateLimit-Limit`: Max requests allowed
  - `X-RateLimit-Remaining`: Requests remaining
  - `X-RateLimit-Reset`: Reset time (ISO 8601)

## Endpoints

### Search Markets

Find prediction markets on Polymarket. The search uses smart relevance scoring to return the best matches first.

```http
GET /markets/search?q={query}
```

**Parameters:**
- `q` (required for search): Natural language query - "bitcoin 15m", "trump election", "superbowl winner"
- `limit` (optional): Max results (default: 10, max: 100)
- `offset` (optional): Pagination offset
- `sort` (optional): "relevance" (default), "volume", "liquidity", "end_date", "recent"
- `ending_soon` (optional): Prioritize markets ending within 24h (default: false)
- `min_volume` (optional): Minimum volume in USD (default: 100)
- `min_liquidity` (optional): Minimum liquidity in USD
- `tag` (optional): Filter by category: "crypto", "politics", "sports", "entertainment"
- `accepting_orders` (optional): Only tradeable markets (default: true)

**Search Tips:**
- **Shorthand supported:** "btc 15m" → "Bitcoin Up or Down", "eth daily" → "Ethereum Up or Down on"
- The search auto-expands: btc→Bitcoin, eth→Ethereum, sol→Solana, etc.
- Time keywords (15m, hourly, daily) auto-expand to "Up or Down" queries
- Results are ranked by relevance: query match + liquidity + volume + accepting orders
- For time-sensitive markets, add `ending_soon=true` to prioritize markets expiring within 24h
- First result is usually the best match - check `context.topMatch`

**Example - Find Current BTC Market:**
```bash
curl -H "Authorization: Bearer $LUCKYLOBSTER_API_KEY" \
  "https://luckylobster.io/api/agent/v1/markets/search?q=bitcoin%20up%20down&ending_soon=true&limit=5"
```

**Example - High-Volume Politics Markets:**
```bash
curl -H "Authorization: Bearer $LUCKYLOBSTER_API_KEY" \
  "https://luckylobster.io/api/agent/v1/markets/search?q=election&tag=politics&sort=volume"
```

**Response:**
```json
{
  "success": true,
  "data": [
    {
      "id": "1314069",
      "slug": "bitcoin-up-or-down-on-february-3",
      "question": "Bitcoin Up or Down on February 3?",
      "outcomes": ["Up", "Down"],
      "outcomePrices": ["0.65", "0.35"],
      "volume": "409100.65",
      "liquidity": "39255.13",
      "endDate": "2026-02-03T17:00:00Z",
      "active": true,
      "acceptingOrders": true
    }
  ],
  "pagination": { "limit": 5, "offset": 0, "count": 1, "hasMore": false },
  "context": {
    "hasResults": true,
    "topMatch": { "id": "1314069", "question": "Bitcoin Up or Down on February 3?", "acceptingOrders": true },
    "endingSoonCount": 1
  },
  "options": {
    "sortBy": ["relevance", "volume", "liquidity", "end_date", "recent"],
    "tags": ["crypto", "politics", "sports", "entertainment"]
  }
}
```

**Workflow for Trading:**
1. Search: `GET /markets/search?q=bitcoin up down`
2. Use the `id` from the top result to get full details: `GET /markets/{id}`
3. Response includes `clobTokenIds` - use these with trading endpoints

---

### Quick Crypto Market Lookup

For crypto up/down markets, use this dedicated endpoint. It uses **deterministic slug-based lookups** (not text search) and is the most reliable way to find crypto markets:

```http
GET /markets/crypto?asset={btc|eth|sol|xrp|doge|matic}&timeframe={daily|hourly|15m}
```

**Examples:**
- `/markets/crypto?asset=btc` - Today's Bitcoin daily market
- `/markets/crypto?asset=btc&timeframe=15m` - Current Bitcoin 15-minute market
- `/markets/crypto?asset=eth&timeframe=hourly` - Current Ethereum hourly market
- `/markets/crypto?asset=xrp&timeframe=15m` - Current XRP 15-minute market

**Response includes `tokens` array with `tokenId`, `negRisk`, and live spread data ready for trading.**

---

### Find Market by Slug

If you know the exact market slug (from a Polymarket URL), use this for direct lookup:

```http
GET /markets/by-slug?slug={slug}
```

**Example:** For URL `https://polymarket.com/event/btc-updown-15m-1770129900`
```bash
curl -H "Authorization: Bearer $LUCKYLOBSTER_API_KEY" \
  "https://luckylobster.io/api/agent/v1/markets/by-slug?slug=btc-updown-15m-1770129900"
```

Response includes `clobTokenIds` and `tokens` ready for trading.

**Note:** For crypto markets, always prefer `/markets/crypto` over `/markets/search` — it uses the same deterministic slug lookups as the internal market-data-worker and is far more reliable.

### Get Market Details

Get detailed information about a specific market, **including token IDs required for market data and trading**.

```http
GET /markets/{id}
```

**Parameters:**
- `id`: Market ID or condition ID (from search results)

**Example:**
```bash
curl -H "Authorization: Bearer $LUCKYLOBSTER_API_KEY" \
  "https://luckylobster.io/api/agent/v1/markets/0x1234..."
```

**Response:**
```json
{
  "success": true,
  "data": {
    "id": "1314069",
    "conditionId": "0xf46bf33576e8341821161316705ab2357312f58d58b7d157cb8dca73b656b326",
    "question": "Bitcoin Up or Down on February 3?",
    "outcomes": ["Up", "Down"],
    "outcomePrices": ["0.345", "0.655"],
    "tokens": [
      {"tokenId": "36656454529662513...", "outcome": "Up", "price": "0.345"},
      {"tokenId": "10609233133841503...", "outcome": "Down", "price": "0.655"}
    ],
    "clobTokenIds": ["36656454529662513...", "10609233133841503..."],
    "volume": "409100.65",
    "liquidity": "39255.13",
    "active": true,
    "acceptingOrders": true,
    "spreads": [
      {"outcome": "Up", "tokenId": "36656454...", "bid": "0.34", "ask": "0.35", "spread": "0.01"}
    ]
  }
}
```

**Important:** Use the `tokenId` from the `tokens` array or `clobTokenIds` for the market data endpoints below.

---

## Market Data Endpoints

These endpoints provide real-time order book and pricing data from the Polymarket CLOB (Central Limit Order Book).

**Workflow for getting market data:**
1. Search markets: `GET /markets/search?q=Bitcoin`
2. Get market details: `GET /markets/{id}` → This returns `tokens[].tokenId`
3. Get market data: `GET /orderbook?token_id={tokenId}` or `GET /market-data?token_id={tokenId}`

### Get Order Book

Get the order book summary for a token, including all bids and asks.

```http
GET /orderbook?token_id={tokenId}
```

**Parameters:**
- `token_id` (required): The outcome token address
- `token_ids` (optional): Comma-separated list for batch request (max 20)

**Example:**
```bash
curl -H "Authorization: Bearer $LUCKYLOBSTER_API_KEY" \
  "https://luckylobster.io/api/agent/v1/orderbook?token_id=71321045..."
```

**Response:**
```json
{
  "success": true,
  "data": {
    "tokenId": "71321045...",
    "market": "0xabc...",
    "timestamp": "2025-01-15T12:00:00Z",
    "bids": [
      {"price": "0.65", "size": "1000"},
      {"price": "0.64", "size": "500"}
    ],
    "asks": [
      {"price": "0.66", "size": "800"},
      {"price": "0.67", "size": "1200"}
    ],
    "tickSize": "0.01",
    "minOrderSize": "1",
    "summary": {
      "bidCount": 15,
      "askCount": 12,
      "bestBid": "0.65",
      "bestAsk": "0.66",
      "spread": "0.01",
      "totalBidSize": "5000.00",
      "totalAskSize": "4200.00"
    }
  }
}
```

### Get Prices

Get current prices for a token including midpoint, buy/sell prices, and last trade.

**Real-time data:** When you have open positions, the server automatically subscribes to Polymarket WebSocket feeds for those markets. Price data from `/prices` and `/heartbeat` will use cached real-time data when available (indicated by `"source": "websocket"` in the response). This is significantly faster than HTTP polling.

```http
GET /prices?token_id={tokenId}
```

**Parameters:**
- `token_id` (required): The outcome token address
- `side` (optional): `BUY` or `SELL` to get price for specific side
- `token_ids` (optional): Comma-separated list for batch request (max 20)

**Example:**
```bash
curl -H "Authorization: Bearer $LUCKYLOBSTER_API_KEY" \
  "https://luckylobster.io/api/agent/v1/prices?token_id=71321045..."
```

**Response:**
```json
{
  "success": true,
  "data": {
    "tokenId": "71321045...",
    "midpoint": "0.655",
    "lastTradePrice": "0.65",
    "buyPrice": "0.66",
    "sellPrice": "0.65",
    "timestamp": "2025-01-15T12:00:00Z",
    "source": "websocket"
  }
}
```

### Get Spread

Get the bid-ask spread for a token.

```http
GET /spread?token_id={tokenId}
```

**Parameters:**
- `token_id` (required): The outcome token address
- `token_ids` (optional): Comma-separated list for batch request (max 20)

**Example:**
```bash
curl -H "Authorization: Bearer $LUCKYLOBSTER_API_KEY" \
  "https://luckylobster.io/api/agent/v1/spread?token_id=71321045..."
```

**Response:**
```json
{
  "success": true,
  "data": {
    "tokenId": "71321045...",
    "bid": "0.65",
    "ask": "0.66",
    "spread": "0.01",
    "spreadPercent": "1.52",
    "timestamp": "2025-01-15T12:00:00Z"
  }
}
```

### Get Comprehensive Market Data

Get all market data in a single request (order book summary, prices, spread).

```http
GET /market-data?token_id={tokenId}
```

**Parameters:**
- `token_id` (required): The outcome token address

**Example:**
```bash
curl -H "Authorization: Bearer $LUCKYLOBSTER_API_KEY" \
  "https://luckylobster.io/api/agent/v1/market-data?token_id=71321045..."
```

**Response:**
```json
{
  "success": true,
  "data": {
    "tokenId": "71321045...",
    "prices": {
      "midpoint": "0.655",
      "bestBid": "0.65",
      "bestAsk": "0.66",
      "lastTrade": "0.65"
    },
    "spread": {
      "absolute": "0.01",
      "percent": "1.52"
    },
    "orderbook": {
      "bidCount": 15,
      "askCount": 12,
      "totalBidSize": "5000.00",
      "totalAskSize": "4200.00",
      "topBids": [{"price": "0.65", "size": "1000"}],
      "topAsks": [{"price": "0.66", "size": "800"}]
    },
    "parameters": {
      "tickSize": "0.01",
      "minOrderSize": "1"
    },
    "timestamp": "2025-01-15T12:00:00Z"
  }
}
```

---

## Trading Endpoints

### Place Order

Place a buy or sell order on a market. Returns real-time order status.

```http
POST /orders
Content-Type: application/json

{
  "tokenId": "0x1234...",
  "side": "BUY",
  "price": 0.65,
  "size": 50,
  "type": "LIMIT"
}
```

**Parameters:**
- `tokenId`: Outcome token address (from market data)
- `side`: `BUY` or `SELL`
- `price`: Price per share (0.01 to 0.99)
- `size`: Number of shares
- `type`: `LIMIT`, `MARKET`, `FOK`, or `FAK`

**Constraints:**
- Minimum order amount: `price * size` must be >= $1.00 USDC
- Minimum order size: 5 shares
- Price must be a multiple of 0.01 (tick size)

**Order Type Behavior:**
- `MARKET`: Fills as much as available at your price or better, kills the rest (recommended for most trades)
- `LIMIT`: Rests on the order book until filled or cancelled
- `FOK`: Fill-or-Kill — must fill 100% or the entire order is rejected. Use only when you need guaranteed full fills. High failure rate on thin markets.
- `FAK`: Fill-and-Kill — same as MARKET

**Tip:** For short-timeframe markets (5m, 15m) where order books can be thin, prefer `MARKET` or `LIMIT` over `FOK`. MARKET orders will fill whatever is available. LIMIT orders rest on the book and can fill as liquidity arrives.

**Real-time Status:** The response includes the order's current status from Polymarket. MARKET and FAK orders return faster (~500ms) since they execute immediately. LIMIT orders are checked after ~1 second.

**Example:**
```bash
curl -X POST -H "Authorization: Bearer $LUCKYLOBSTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tokenId":"0x1234...","side":"BUY","price":0.65,"size":50,"type":"LIMIT"}' \
  "https://luckylobster.io/api/agent/v1/orders"
```

**Response (with fill data when available):**
```json
{
  "success": true,
  "data": {
    "order": {
      "id": "ord_abc123",
      "polyOrderId": "0x...",
      "status": "FILLED",
      "side": "BUY",
      "price": 0.65,
      "size": 50,
      "filledSize": 50,
      "avgFillPrice": 0.65,
      "transactionHash": "0x...",
      "filledAt": "2025-01-15T12:00:01Z"
    }
  }
}
```

### List Orders

Get your orders with real-time status from Polymarket.

```http
GET /orders?status=OPEN&limit=50
```

**Parameters:**
- `status` (optional): Filter by status - `PENDING`, `OPEN`, `FILLED`, `PARTIALLY_FILLED`, `CANCELLED`, `EXPIRED`, `FAILED`
- `limit` (optional): Max results (default: 50, max: 100)
- `offset` (optional): Pagination offset
- `sync` (optional): Set to `false` to skip live status sync (default: true)

**Real-time Sync:** Open orders (PENDING, OPEN, PARTIALLY_FILLED) are automatically synced with Polymarket in parallel for real-time status. The response includes sync metadata.

**Example:**
```bash
curl -H "Authorization: Bearer $LUCKYLOBSTER_API_KEY" \
  "https://luckylobster.io/api/agent/v1/orders?status=OPEN"
```

**Response includes sync info:**
```json
{
  "success": true,
  "data": [...],
  "sync": { "enabled": true, "updated": 2 }
}
```

### Get Order Status

Get details for a specific order, including live status from Polymarket.

```http
GET /orders/{orderId}
```

**Example:**
```bash
curl -H "Authorization: Bearer $LUCKYLOBSTER_API_KEY" \
  "https://luckylobster.io/api/agent/v1/orders/ord_abc123"
```

**Response:**
```json
{
  "success": true,
  "data": {
    "order": {
      "id": "ord_abc123",
      "polyOrderId": "0x...",
      "tokenId": "123456...",
      "side": "BUY",
      "type": "LIMIT",
      "price": "0.65",
      "size": "50",
      "filledSize": "25",
      "status": "PARTIALLY_FILLED",
      "marketQuestion": "Bitcoin Up or Down?",
      "outcome": "Up",
      "submittedAt": "2025-01-15T12:00:00Z"
    },
    "liveStatus": {
      "polymarketStatus": "LIVE",
      "originalSize": 50,
      "sizeMatched": 25,
      "price": 0.65
    }
  }
}
```

### Cancel Order

Cancel an open order. Only orders with status `PENDING`, `OPEN`, or `PARTIALLY_FILLED` can be cancelled.

```http
DELETE /orders/{orderId}
```

**Example:**
```bash
curl -X DELETE -H "Authorization: Bearer $LUCKYLOBSTER_API_KEY" \
  "https://luckylobster.io/api/agent/v1/orders/ord_abc123"
```

**Response:**
```json
{
  "success": true,
  "message": "Order cancelled successfully",
  "data": {
    "order": {
      "id": "ord_abc123",
      "polyOrderId": "0x...",
      "status": "CANCELLED",
      "previousStatus": "OPEN",
      "filledSize": "25",
      "cancelledAt": "2025-01-15T12:05:00Z"
    }
  }
}
```

**Error Responses:**
- `400`: Order cannot be cancelled (already filled, cancelled, or failed)
- `403`: Order was not placed by your agent
- `404`: Order not found

**Notes:**
- You can only cancel orders that your agent placed
- If an order is partially filled, cancelling stops further fills but keeps the filled portion
- The `previousStatus` field shows what status the order had before cancellation

### Close Position (One-Shot)

Close an entire position with a single API call. The server handles determining the correct side, fetching current price, and placing the market order.

```http
POST /positions/{positionId}/close
```

**URL Parameters:**
- `positionId`: Position ID from `GET /positions`

**Optional Body:**
```json
{
  "type": "MARKET",   // Order type: "MARKET" (default), "FOK", or "LIMIT"
  "slippage": 0.02,   // Max slippage for LIMIT orders (default: 2%)
  "dryRun": true      // Preview without executing
}
```

**Example - Close a position:**
```bash
curl -X POST -H "Authorization: Bearer $LUCKYLOBSTER_API_KEY" \
  "https://luckylobster.io/api/agent/v1/positions/pos_abc123/close"
```

**Example - Preview first (dry run):**
```bash
curl -X POST -H "Authorization: Bearer $LUCKYLOBSTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"dryRun": true}' \
  "https://luckylobster.io/api/agent/v1/positions/pos_abc123/close"
```

**Response:**
```json
{
  "success": true,
  "message": "Position closed successfully",
  "data": {
    "order": {
      "id": "ord_xyz789",
      "polyOrderId": "0x...",
      "status": "FILLED",
      "side": "SELL",
      "type": "MARKET",
      "price": 0.65,
      "size": 100,
      "filledSize": 100,
      "avgFillPrice": 0.65,
      "transactionHash": "0x..."
    },
    "position": {
      "id": "pos_abc123",
      "remainingSize": 0,
      "isClosed": true
    },
    "execution": {
      "proceeds": 65.00,
      "pnl": 10.00,
      "pnlPercent": 18.18
    },
    "market": {
      "slug": "bitcoin-up-or-down-on-february-3",
      "question": "Bitcoin Up or Down on February 3?",
      "outcome": "Up"
    }
  }
}
```

**Dry Run Response:**
```json
{
  "success": true,
  "dryRun": true,
  "message": "Position close preview - no order placed",
  "data": {
    "position": {
      "id": "pos_abc123",
      "tokenId": "123456...",
      "size": 100,
      "avgEntryPrice": 0.55
    },
    "closeOrder": {
      "side": "SELL",
      "type": "MARKET",
      "price": 0.65,
      "size": 100
    },
    "estimates": {
      "currentPrice": 0.65,
      "bidPrice": 0.65,
      "proceeds": 65.00,
      "pnl": 10.00,
      "pnlPercent": 18.18
    }
  }
}
```

**Error Responses:**
- `400`: Position is already closed (size=0) or already settled
- `403`: Position belongs to another user
- `404`: Position not found
- `503`: Unable to fetch market price (market may be closed/illiquid)

**Notes:**
- Uses the current bid price for immediate execution
- For LIMIT orders, applies slippage tolerance (default 2%) below bid
- Position is partially closed if order only partially fills
- Check `position.isClosed` to verify complete exit

### Get Balance

Get raw wallet balance (live from chain).

```http
GET /balance
```

**Response:**
```json
{
  "success": true,
  "data": {
    "usdc": 93.16,
    "matic": 0.5,
    "address": "0x1234...",
    "walletType": "proxy"
  }
}
```

**Fields:**
- `usdc`: Raw USDC in wallet
- `matic`: For gas (rarely needed)
- `address`: Your trading wallet on Polygon

### Approve Tokens (Fix "not enough allowance" errors)

If you get a "not enough balance / allowance" error when selling or closing positions, you need to approve the CTF (Conditional Token Framework) contract. This is a one-time setup per wallet.

```http
POST /wallet/approve
```

**Request Body:**
```json
{
  "token": "CTF"  // or "USDC", "NEG_RISK", "all"
}
```

**Token Types:**
- `USDC`: Required for buying (usually already approved)
- `CTF`: **Required for selling/closing positions**
- `NEG_RISK`: Required for multi-outcome markets
- `all`: Approve all tokens at once

**Example - Fix selling errors:**
```bash
curl -X POST -H "Authorization: Bearer $LUCKYLOBSTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"token": "CTF"}' \
  "https://luckylobster.io/api/agent/v1/wallet/approve"
```

**Response:**
```json
{
  "success": true,
  "message": "CTF approved successfully",
  "data": {
    "approvals": { "CTF": "0x..." },
    "successful": ["CTF"],
    "failed": []
  }
}
```

**When to use:**
- Error: "not enough balance / allowance" when selling
- Error: "not enough balance / allowance" when closing a position
- First time selling after wallet setup

### Get Budget

Get how much you can spend. **Use this before placing orders.**

Budget = min(wallet balance, fixed limit, % of wallet)

```http
GET /budget
```

**Response:**
```json
{
  "success": true,
  "data": {
    "usdc": 46.58,
    "limitedBy": "percent",
    "wallet": 93.16,
    "config": {
      "fixedLimit": null,
      "budgetPercent": 50,
      "maxPositionValue": null,
      "used": 0
    }
  }
}
```

**Fields:**
- `usdc`: **What you can spend** (accounts for all limits)
- `limitedBy`: Why you're capped (`"wallet"`, `"fixed_limit"`, `"percent"`, `"position_limit"`)
- `wallet`: Raw wallet balance
- `config`: Your budget settings

### Get Positions

Get your current positions directly from Polymarket. The endpoint fetches from the **Polymarket Data API** (source of truth) with automatic fallback to CLOB trades if Data API returns empty.

```http
GET /positions
```

**Parameters:**
- `status` (optional): Filter by status - `"open"` (default), `"settled"`, `"redeemable"`, or `"all"`
- `token_id` (optional): Filter by specific token ID
- `condition_id` (optional): Filter by condition ID (market)
- `source` (optional): Force data source - `"clob"` to bypass Data API and fetch directly from CLOB trades

**Example - Get Open Positions:**
```bash
curl -H "Authorization: Bearer $LUCKYLOBSTER_API_KEY" \
  "https://luckylobster.io/api/agent/v1/positions"
```

**Example - Get Redeemable Positions:**
```bash
curl -H "Authorization: Bearer $LUCKYLOBSTER_API_KEY" \
  "https://luckylobster.io/api/agent/v1/positions?status=redeemable"
```

**Example - Force CLOB Source (for freshest data):**
```bash
curl -H "Authorization: Bearer $LUCKYLOBSTER_API_KEY" \
  "https://luckylobster.io/api/agent/v1/positions?source=clob"
```

**Response:**
```json
{
  "success": true,
  "data": {
    "positions": [
      {
        "id": "0xabc123_0",
        "tokenId": "12345...",
        "conditionId": "0xabc123...",
        "market": {
          "slug": "bitcoin-up-or-down-on-february-3",
          "question": "Bitcoin Up or Down on February 3?",
          "outcome": "Up",
          "endDate": "2026-02-03T17:00:00Z",
          "eventSlug": "btc-updown"
        },
        "size": 100.0,
        "avgEntryPrice": 0.55,
        "currentPrice": 0.62,
        "currentValue": 62.00,
        "pnl": {
          "unrealized": 7.00,
          "unrealizedPercent": 12.73,
          "realized": 0.00,
          "realizedPercent": 0.00
        },
        "status": {
          "isOpen": true,
          "isSettled": false,
          "isRedeemable": false,
          "isMergeable": false,
          "isNegRisk": false
        },
        "oppositeOutcome": "Down",
        "oppositeAsset": "67890...",
        "updatedAt": "2026-02-03T14:45:00.000Z"
      }
    ],
    "summary": {
      "totalPositions": 1,
      "totalUnrealizedPnl": 7.00,
      "totalRealizedPnl": 0.00,
      "totalValue": 62.00
    },
    "filters": {
      "status": "open",
      "tokenId": null,
      "conditionId": null
    },
    "wallet": {
      "address": "0x539b2de928064898...",
      "type": "proxy"
    },
    "source": "data-api"
  }
}
```

**Key Fields:**
- `size`: Number of outcome tokens held
- `avgEntryPrice`: Average price paid per token (0.00 to 1.00)
- `currentPrice`: Live price from Polymarket
- `currentValue`: Current value in USDC
- `pnl.unrealized`: Unrealized profit/loss
- `pnl.unrealizedPercent`: Unrealized P&L as percentage
- `status.isRedeemable`: `true` if market resolved and position can be redeemed
- `status.isNegRisk`: `true` for multi-outcome markets (require special redemption)
- `wallet.type`: `"proxy"` (Polymarket smart wallet) or `"eoa"` (direct wallet)
- `source`: `"data-api"` or `"clob-trades"` - shows which data source was used

**Data Source Behavior:**
1. **Default**: Fetches from Polymarket Data API (most reliable, includes all historical positions)
2. **Fallback**: If Data API returns empty, automatically falls back to CLOB trades
3. **Forced CLOB**: Use `?source=clob` for freshest data immediately after placing an order (Data API can have ~30s delay)

**When to use `source=clob`:**
- Right after placing an order (Data API has indexing delay)
- When Data API returns fewer positions than expected
- For debugging position discrepancies

---

## Settlement & Redemption

When a market resolves, winning outcome tokens can be redeemed for $1.00 USDC each. The redemption endpoint automatically fetches all redeemable positions from Polymarket and redeems them in a single batched transaction.

### List Redeemable Positions

Check what positions are ready for redemption.

```http
GET /settlements/redeem
```

**Response:**
```json
{
  "success": true,
  "data": {
    "positions": [
      {
        "conditionId": "0xbc13af0a940bb9a...",
        "title": "Bitcoin Up or Down - February 4, 1:45PM-2:00PM ET",
        "outcome": "Down",
        "size": 5.0,
        "estimatedValue": "$5.00",
        "negRisk": false
      }
    ],
    "count": 3,
    "totalValue": "$42.50",
    "wallet": "0x539b2de928064898..."
  },
  "message": "Found 3 redeemable positions (~$42.50). POST to redeem."
}
```

### Redeem Positions

Redeem winning positions in a single batched transaction. You can redeem all positions or target specific ones.

```http
POST /settlements/redeem
```

**Optional Parameters:**
```json
{
  "conditionId": "0x...",           // Redeem ONLY this specific position
  "conditionIds": ["0x...", "..."], // Redeem ONLY these specific positions
  "limit": 50,       // Max positions to redeem (default: 10, max: 50)
  "minValue": 1.00,  // Skip positions below this value (default: $0.10)
  "dryRun": true     // Preview what would be redeemed without executing
}
```

**Example - Redeem a specific position:**
```bash
curl -X POST -H "X-API-Key: $LUCKYLOBSTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"conditionId": "0xbc13af0a940bb9a..."}' \
  "https://luckylobster.io/api/agent/v1/settlements/redeem"
```

**Example - Redeem all:**
```bash
curl -X POST -H "X-API-Key: $LUCKYLOBSTER_API_KEY" \
  "https://luckylobster.io/api/agent/v1/settlements/redeem"
```

**Example - Dry run first:**
```bash
curl -X POST -H "X-API-Key: $LUCKYLOBSTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"dryRun": true}' \
  "https://luckylobster.io/api/agent/v1/settlements/redeem"
```

**Example - Redeem up to 50, skip dust:**
```bash
curl -X POST -H "X-API-Key: $LUCKYLOBSTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"limit": 50, "minValue": 1.00}' \
  "https://luckylobster.io/api/agent/v1/settlements/redeem"
```

**Response:**
```json
{
  "success": true,
  "message": "Redeemed 3 positions (~$42.50)",
  "data": {
    "processed": 3,
    "redeemed": [
      {
        "conditionId": "0xbc13af0a...",
        "title": "Bitcoin Up or Down - February 4, 1:45PM-2:00PM ET",
        "outcome": "Down",
        "size": 5.0,
        "txHash": "0x82d8d7e4d63185..."
      }
    ],
    "failed": [],
    "remaining": 0,
    "totalValueRedeemed": "$42.50"
  }
}
```

**Error Responses:**
- `404`: Wallet not found
- `403`: Missing "redeem" permission
- `501`: Polymarket Builder API not configured

**Notes:**
- Redemption is gasless (executed via Polymarket relayer)
- All positions are batched into a single on-chain transaction for efficiency
- Only positions marked as "redeemable" by Polymarket are included
- Both standard and NegRisk markets are handled automatically

---

## Heartbeat (Status Check)

The heartbeat endpoint provides a single-call summary of your portfolio, pending actions, and account status. Call it periodically to stay informed.

### Check Heartbeat

```http
GET /heartbeat?skill_version=10
```

Include `skill_version` so the server can indicate if a newer version is available.

Returns aggregated data: open positions, redeemable positions, filled orders since last check, budget status, and items that may need attention.

**Response:**
```json
{
  "success": true,
  "data": {
    "status": "ACTION_NEEDED",
    "timestamp": "2026-02-06T15:30:00Z",
    "actions": [
      {
        "type": "REDEEM",
        "priority": "high",
        "summary": "2 positions ready to redeem (~$42.50)",
        "details": { "positions": [...], "action": "POST /settlements/redeem" }
      },
      {
        "type": "ORDER_FILLED",
        "priority": "medium",
        "summary": "3 order(s) filled since last check",
        "details": { "fills": [...], "totalPnl": 12.50 }
      }
    ],
    "portfolio": {
      "openPositions": 5,
      "totalValue": 250.00,
      "unrealizedPnl": 12.50,
      "redeemableCount": 2,
      "redeemableValue": 42.50
    },
    "orderActivity": {
      "openOrders": 1,
      "filledSinceLastHeartbeat": 3,
      "recentFills": [...]
    },
    "budget": {
      "available": 46.58,
      "limitedBy": "percent",
      "walletBalance": 93.16
    },
    "heartbeat": {
      "next_check_at": "2026-02-06T16:00:00Z",
      "interval_ms": 1800000,
      "active_hours": { "start": "09:00", "end": "22:00", "timezone": "America/New_York" },
      "heartbeat_count": 42,
      "latest_skill_version": 10
    },
    "realtime": {
      "subscribedMarkets": 3,
      "clobConnected": true,
      "sportsConnected": false,
      "cryptoConnected": true
    }
  }
}
```

**Status Values:**
- `ACTION_NEEDED`: Items may need attention. Check the `actions` array.
- `OK`: Nothing needs attention.
- `SLEEPING`: Outside configured active hours.

**Action Types:**
- `REDEEM` (high): Settled positions ready for redemption. Call `POST /settlements/redeem`.
- `ORDER_FILLED` (medium): Orders filled since last heartbeat. Includes P&L details.
- `BUDGET_LOW` (medium): Budget running low.
- `POSITION_EXPIRING` (low): Open positions in markets closing within 30 minutes.
- `SEEK_TRADES` (low): Budget is available. Trading opportunities may exist — use `GET /markets/search` or `GET /markets/crypto` to browse.

**Heartbeat Scheduling:**

The response includes `heartbeat.next_check_at` as a suggested time for your next call, and `heartbeat.latest_skill_version` so you can check whether a newer skill version is available on ClawHub. Use the suggested interval or your own preferred schedule.

---

## Autonomous Strategies

Create server-side trading strategies that execute automatically. Instead of making trade decisions on every heartbeat (which costs tokens), define rules once and let the server trade for you.

### Strategy Types

**PRICE_ALERT** - Buy or sell when price hits a target (stop-loss, take-profit, limit buy)
**RECURRING_BUY** - Dollar-cost average into a market at fixed intervals
**BUY_LOW_SELL_HIGH** - Buy below a floor, sell above a ceiling, repeat

### Create Strategy

```
POST ${baseUrl}/api/agent/v1/strategies
Authorization: Bearer {api_key}

// PRICE_ALERT example: Sell if price drops below 0.35
{
  "name": "BTC Stop-Loss",
  "type": "PRICE_ALERT",
  "config": {
    "marketQuery": "bitcoin",
    "outcome": "Yes",
    "side": "SELL",
    "triggerCondition": "PRICE_LTE",
    "triggerPrice": 0.35,
    "size": 100,
    "orderType": "MARKET"
  },
  "maxBudget": 50
}

// RECURRING_BUY example: Buy $10 of Bitcoin every hour
{
  "name": "BTC Recurring Hourly",
  "type": "RECURRING_BUY",
  "config": {
    "marketQuery": "bitcoin",
    "outcome": "Yes",
    "amountPerInterval": 10,
    "interval": "1h",
    "maxTotalAmount": 200,
    "priceLimit": 0.70
  },
  "maxBudget": 200
}

// BUY_LOW_SELL_HIGH example: Buy below 0.40, sell above 0.60
{
  "name": "BTC Range Trader",
  "type": "BUY_LOW_SELL_HIGH",
  "config": {
    "marketQuery": "bitcoin",
    "outcome": "Yes",
    "buyBelow": 0.40,
    "sellAbove": 0.60,
    "sizePerTrade": 50,
    "maxOpenSize": 200
  },
  "maxBudget": 500
}

// COPY_TRADE example: Mirror a wallet's trades with $10 per copy
{
  "name": "Copy Whale",
  "type": "COPY_TRADE",
  "config": {
    "targetAddress": "0x1234567890abcdef1234567890abcdef12345678",
    "sizingMode": "fixed",
    "fixedAmount": 10,
    "copySells": true,
    "maxPositionSize": 500
  },
  "maxBudget": 500
}

// COPY_TRADE proportional example: Copy 50% of target's trade size
{
  "name": "Mirror Trader",
  "type": "COPY_TRADE",
  "config": {
    "targetAddress": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd",
    "sizingMode": "proportional",
    "proportionPct": 50,
    "copySells": true
  },
  "maxBudget": 1000
}
```

### COPY_TRADE Config Fields

| Field | Required | Description |
|-------|----------|-------------|
| `targetAddress` | Yes | Wallet address to copy (0x + 40 hex chars) |
| `sizingMode` | Yes | `"fixed"` (fixed USDC per trade) or `"proportional"` (% of target's size) |
| `fixedAmount` | When fixed | USDC to spend per copy trade |
| `proportionPct` | When proportional | 1-100, percentage of target's trade size |
| `copySells` | Yes | `true` to also copy sell trades, `false` for buys only |
| `maxPositionSize` | No | Max shares to hold per token |
| `tokenFilter` | No | Array of token IDs — only copy trades for these tokens |

Copy trading monitors the Polygon blockchain in real-time (~2 second detection). It cannot copy your own wallet.

### List Strategies

```
GET ${baseUrl}/api/agent/v1/strategies
GET ${baseUrl}/api/agent/v1/strategies?status=ACTIVE&type=DCA
```

### Get Strategy Details

```
GET ${baseUrl}/api/agent/v1/strategies/{id}
```

Returns full strategy with last 20 execution records.

### Update Strategy

```
PATCH ${baseUrl}/api/agent/v1/strategies/{id}
{ "config": { ... }, "maxBudget": 300 }
```

### Pause / Resume / Cancel

```
POST ${baseUrl}/api/agent/v1/strategies/{id}/pause
POST ${baseUrl}/api/agent/v1/strategies/{id}/resume
DELETE ${baseUrl}/api/agent/v1/strategies/{id}
```

### Strategy Lifecycle

1. **ACTIVE** - Server evaluates every ~10 seconds
2. **PAUSED** - Paused by user (from dashboard) or agent. Not evaluated until resumed.
3. **ERROR** - Auto-paused after 3 consecutive failures. Use `POST /strategies/{id}/resume` to retry.
4. **COMPLETED** - Budget exhausted or one-shot trigger fired
5. **CANCELLED** - Cancelled by user (from dashboard) or agent. Cannot be resumed.
6. **EXPIRED** - Past 24-hour default TTL (or custom `expiresAt`). Cannot be resumed.

### Crypto Market Auto-Discovery

For strategies using `marketQuery` instead of `tokenId`: the server automatically discovers the current active market each evaluation. For known crypto assets (btc, eth, sol, xrp, etc.), this uses **deterministic slug-based lookups** — the same approach as the market-data-worker. This reliably handles Polymarket's ephemeral crypto markets (15-min, hourly, daily) that expire and are replaced. Non-crypto queries fall back to text search.

### COPY_TRADE Real-Time Monitoring

Copy trade strategies use blockchain event monitoring (not polling) to detect target wallet activity in ~2 seconds. The server watches ERC1155 transfer events on Polymarket's CTF contract via Alchemy WebSocket RPC. Events are deduplicated, so reconnections don't cause double-execution.

### Heartbeat Integration

The heartbeat response includes a `strategies` section and these action types:
- **STRATEGY_EXECUTED** (medium) — Trades placed by your strategies
- **STRATEGY_ERROR** (high) — Strategies auto-paused after consecutive errors
- **STRATEGY_COMPLETED** (low) — Strategies that finished (budget exhausted or trigger fired)
- **STRATEGY_PAUSED** (high) — Strategy paused by the user from the dashboard. `POST /strategies/{id}/resume` to restart.
- **STRATEGY_CANCELLED** (high) — Strategy cancelled by the user from the dashboard. Create a new strategy if needed.

### Best Practices

1. **Set maxBudget** on every strategy to limit exposure
2. **Use PRICE_ALERT** for stop-loss/take-profit on existing positions
3. **Use RECURRING_BUY with marketQuery** for crypto markets (handles market expiry)
4. **Review heartbeat** periodically to see strategy execution results
5. **Max 10 active strategies** per agent

---

## Error Handling

```json
{
  "success": false,
  "error": "Error Type",
  "message": "Human-readable message"
}
```

**Status Codes:**
- `401`: Invalid API key
- `403`: Insufficient permissions or budget exceeded
- `404`: Resource not found
- `429`: Rate limit exceeded

## Permissions

- `read`: View markets, orders, positions, balance
- `trade`: Place orders
- `cancel`: Cancel orders
- `redeem`: Redeem settled positions

