PeakBot Docs
Bots EndpointBot Data

Update User Bot State

Update user bot state for multiple account-ticker combinations in bulk

Update user bot state for one or more account-ticker combinations. This endpoint allows bots to persist their internal state (e.g., position tracking, strategy state, indicators) for each user account and ticker combination. State updates are processed independently, so partial failures won't affect successful updates.

Endpoint

POST /api/bots/:slug/state

Authentication

All requests require bot API key authentication via the x-api-key header.

headers: {
  'x-api-key': 'your-bot-api-key'
}

Path Parameters

ParameterTypeRequiredDescription
slugstringYesBot identifier (alphanumeric, hyphens, underscores only)

Slug Validation

The slug must match the regex pattern: /^[a-zA-Z0-9_-]+$/

Request Body

{
  states: Array<{
    accountId: string;      // UUID of user broker account
    tickerId: string;        // UUID of ticker
    state: Record&lt;string, unknown&gt;;  // Bot-specific state object
  }>;
}

Request Body Schema

FieldTypeRequiredDescription
statesarrayYesArray of state update objects
states[].accountIdstring (UUID)YesUUID of the user broker account
states[].tickerIdstring (UUID)YesUUID of the ticker
states[].stateobjectYesBot-specific state object (must be a plain object, not array)

Validation Rules

  • states must be a non-empty array
  • Each accountId must be a valid UUID string
  • Each tickerId must be a valid UUID string
  • Each state must be a plain object (not null, not array, not primitive)
  • All state updates are processed independently using Promise.allSettled, so partial failures are handled gracefully

Response

Success Response (200 OK)

{
  "success": true,
  "summary": {
    "total": 3,
    "succeeded": 2,
    "failed": 1
  },
  "results": [
    {
      "status": "success",
      "data": {
        "id": "111e4567-e89b-12d3-a456-426614174001",
        "botId": "123e4567-e89b-12d3-a456-426614174000",
        "userBrokerAccountId": "550e8400-e29b-41d4-a716-446655440000",
        "tickerId": "222e4567-e89b-12d3-a456-426614174002",
        "state": {
          "lastPrice": 150.25,
          "position": "long"
        }
      },
      "accountId": "550e8400-e29b-41d4-a716-446655440000",
      "tickerId": "222e4567-e89b-12d3-a456-426614174002"
    },
    {
      "status": "success",
      "data": {
        "id": "333e4567-e89b-12d3-a456-426614174003",
        "botId": "123e4567-e89b-12d3-a456-426614174000",
        "userBrokerAccountId": "550e8400-e29b-41d4-a716-446655440001",
        "tickerId": "444e4567-e89b-12d3-a456-426614174004",
        "state": {
          "indicators": {
            "rsi": 65.5,
            "macd": 1.2
          }
        }
      },
      "accountId": "550e8400-e29b-41d4-a716-446655440001",
      "tickerId": "444e4567-e89b-12d3-a456-426614174004"
    },
    {
      "status": "error",
      "error": "Ticker not found",
      "accountId": "550e8400-e29b-41d4-a716-446655440002",
      "tickerId": "555e4567-e89b-12d3-a456-426614174005",
      "index": 2
    }
  ]
}

Response Schema

FieldTypeDescription
successbooleanAlways true for successful requests (even with partial failures)
summaryobjectSummary of update results
summary.totalnumberTotal number of state updates attempted
summary.succeedednumberNumber of successful updates
summary.failednumberNumber of failed updates
resultsarrayArray of individual update results
results[].statusstringEither "success" or "error"
results[].dataobject(Success only) Updated user bot state object
results[].errorstring(Error only) Error message
results[].accountIdstringUUID of the account for this update
results[].tickerIdstringUUID of the ticker for this update
results[].indexnumber(Error only) Index in the original request array

Notes

  • Updates are processed independently, so some may succeed while others fail
  • Successful updates are upserted (created if not exists, updated if exists)
  • The response includes both successful and failed updates for transparency
  • Failed updates are logged server-side but don't cause the entire request to fail

Error Responses

400 Bad Request

Invalid JSON:

{
  "error": "Invalid JSON in request body",
  "code": "BAD_REQUEST"
}

Missing states array:

{
  "error": "states must be an array",
  "code": "BAD_REQUEST"
}

Empty states array:

{
  "error": "states array cannot be empty",
  "code": "BAD_REQUEST"
}

Invalid accountId:

{
  "error": "states[0].accountId is required and must be a string",
  "code": "BAD_REQUEST"
}

Invalid tickerId:

{
  "error": "states[0].tickerId is required and must be a string",
  "code": "BAD_REQUEST"
}

Invalid state object:

{
  "error": "states[0].state is required and must be an object",
  "code": "BAD_REQUEST"
}

Invalid slug format:

{
  "error": "Invalid slug format. Only alphanumeric characters, hyphens, and underscores are allowed.",
  "code": "BAD_REQUEST"
}

401 Unauthorized

Missing API key:

{
  "message": "API key required. Include x-api-key header."
}

403 Forbidden

Invalid API key:

{
  "message": "Invalid API key for this bot"
}

404 Not Found

Bot not found:

{
  "error": "Bot with slug 'my-bot' not found",
  "code": "NOT_FOUND"
}

500 Internal Server Error

{
  "error": "Internal server error",
  "code": "INTERNAL_SERVER_ERROR"
}

Code Examples

TypeScript

interface StateUpdate {
  accountId: string;
  tickerId: string;
  state: Record&lt;string, unknown&gt;;
}

async function updateBotState(
  botSlug: string,
  apiKey: string,
  states: StateUpdate[]
) {
  const response = await fetch(`https://api.example.com/api/bots/${botSlug}/state`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-api-key": apiKey,
    },
    body: JSON.stringify({ states }),
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Failed to update user bot state: ${error.error}`);
  }

  return await response.json();
}

// Example usage - update state for multiple account-ticker combinations
const states = [
  {
    accountId: "550e8400-e29b-41d4-a716-446655440000",
    tickerId: "222e4567-e89b-12d3-a456-426614174002",
    state: {
      lastPrice: 150.25,
      position: "long",
      entryPrice: 145.00,
      stopLoss: 140.00
    }
  },
  {
    accountId: "550e8400-e29b-41d4-a716-446655440001",
    tickerId: "444e4567-e89b-12d3-a456-426614174004",
    state: {
      indicators: {
        rsi: 65.5,
        macd: 1.2,
        ema20: 148.50
      },
      signals: ["buy"]
    }
  }
];

const result = await updateBotState("my-trading-bot", "your-api-key", states);

console.log(`Updated ${result.summary.succeeded} of ${result.summary.total} states`);

// Check for failures
const failures = result.results.filter(r => r.status === "error");
if (failures.length > 0) {
  console.error("Some updates failed:", failures);
}

Python

from typing import List, Dict, Any
import requests

def update_bot_state(
    bot_slug: str,
    api_key: str,
    states: List[Dict[str, Any]]
) -> Dict[str, Any]:
    """
    Update user bot state for multiple account-ticker combinations.
    
    Args:
        bot_slug: Bot identifier
        api_key: Bot API key
        states: List of state update dictionaries, each containing:
            - accountId: UUID of user broker account
            - tickerId: UUID of ticker
            - state: Dictionary of bot-specific state data
            
    Returns:
        Response dictionary with update results
        
    Raises:
        requests.HTTPError: If the request fails
    """
    url = f"https://api.example.com/api/bots/{bot_slug}/state"
    
    payload = {
        "states": states
    }
    
    headers = {
        "Content-Type": "application/json",
        "x-api-key": api_key
    }
    
    response = requests.post(url, json=payload, headers=headers)
    response.raise_for_status()
    
    return response.json()

# Example usage - update state for multiple account-ticker combinations
states = [
    {
        "accountId": "550e8400-e29b-41d4-a716-446655440000",
        "tickerId": "222e4567-e89b-12d3-a456-426614174002",
        "state": {
            "lastPrice": 150.25,
            "position": "long",
            "entryPrice": 145.00,
            "stopLoss": 140.00
        }
    },
    {
        "accountId": "550e8400-e29b-41d4-a716-446655440001",
        "tickerId": "444e4567-e89b-12d3-a456-426614174004",
        "state": {
            "indicators": {
                "rsi": 65.5,
                "macd": 1.2,
                "ema20": 148.50
            },
            "signals": ["buy"]
        }
    }
]

result = update_bot_state("my-trading-bot", "your-api-key", states)

print(f"Updated {result['summary']['succeeded']} of {result['summary']['total']} states")

# Check for failures
failures = [r for r in result["results"] if r["status"] == "error"]
if failures:
    print("Some updates failed:", failures)

Use Cases

Update Position State

// Update state after opening a position
await updateBotState("my-bot", apiKey, [{
  accountId: accountId,
  tickerId: tickerId,
  state: {
    position: "long",
    entryPrice: 150.25,
    quantity: 100,
    stopLoss: 145.00,
    takeProfit: 160.00,
    openedAt: new Date().toISOString()
  }
}]);

Bulk State Update After Market Close

# Update state for all active positions at end of day
states = []
for account_id, ticker_id, position_data in active_positions:
    states.append({
        "accountId": account_id,
        "tickerId": ticker_id,
        "state": {
            "endOfDayPrice": position_data["current_price"],
            "unrealizedPnl": position_data["unrealized_pnl"],
            "lastUpdate": datetime.now().isoformat()
        }
    })

result = update_bot_state("my-bot", api_key, states)
print(f"Updated {result['summary']['succeeded']} positions")

Update Indicator State

// Update technical indicator state
await updateBotState("my-bot", apiKey, [{
  accountId: accountId,
  tickerId: tickerId,
  state: {
    indicators: {
      rsi: calculateRSI(prices),
      macd: calculateMACD(prices),
      bollingerBands: calculateBollingerBands(prices)
    },
    lastCalculation: new Date().toISOString()
  }
}]);