--- name: luckylobster description: Trade prediction markets on Polymarket. Search markets, place orders, and manage positions. homepage: https://luckylobster.io user-invocable: true metadata: {"openclaw":{"version":10,"primaryEnv":"LUCKYLOBSTER_API_KEY","emoji":"🦞","homepage":"https://luckylobster.io","requires":{"env":["LUCKYLOBSTER_API_KEY"]}}} --- # 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.** --- ### All Crypto Markets Snapshot (Recommended for Trading) Get a **single snapshot** of ALL active crypto up/down markets with live prices, oracle data, and spread information. This is the fastest and most efficient way to find trading opportunities - one call instead of many. ```http GET /markets/crypto/all ``` **Optional filters:** - `?asset=btc` - Filter to one asset (BTC, ETH, SOL, XRP) - `?timeframe=15m` - Filter to one timeframe (5m, 15m, 1h, 1d) **Example:** ```bash curl -H "Authorization: Bearer $LUCKYLOBSTER_API_KEY" \ "${baseUrl}/api/agent/v1/markets/crypto/all" ``` **Response:** ```json { "success": true, "markets": [ { "marketId": "BTC-15M", "slug": "btc-updown-15m-1740412800", "conditionId": "0xabc...", "asset": "BTC", "timeframe": "15m", "endsAt": "2026-02-24T19:15:00.000Z", "negRisk": false, "oracleSource": "chainlink", "priceToBeat": "96432.50", "oraclePrice": "96510.23", "up": { "tokenId": "123...", "bid": "0.55", "ask": "0.58", "mid": "0.565", "last": "0.56" }, "down": { "tokenId": "456...", "bid": "0.40", "ask": "0.43", "mid": "0.415", "last": "0.42" }, "combinedAsk": 1.01, "spread": -1.0, "midpoint": 0.98 } ], "meta": { "count": 20, "workerAlive": true, "ts": 1740422100000 } } ``` **Key fields:** - `priceToBeat` - Oracle price at market open (resolution reference) - `oraclePrice` - Current oracle spot price (Chainlink for 5m/15m, Binance for 1h/1d) - `up`/`down` - Contract bid/ask/mid/last for each side. Use `tokenId` for trading. - `combinedAsk` - `up.ask + down.ask` (cost to buy both sides) - `spread` - `(1 - combinedAsk) * 100` (negative = overpriced vs $1 payout) - `midpoint` - `up.mid + down.mid` (combined midpoint) **Why use this:** In one API call you get everything needed to evaluate and trade any crypto market. No need to call `/markets/crypto` then `/spread` then `/prices` separately. --- ### 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