{
  "openapi": "3.1.0",
  "info": {
    "title": "Agent Bobby public research API",
    "version": "1.0.0",
    "description": "Free, read-only public preview from Loxidev LLC. No API key. Cached stock research and market pulse; not execution data. Use source observation timestamps, not generatedAt. Missing values are null. Source terms apply; no bulk redistribution grant. Best-effort per-instance protection: 30 requests/minute/IP. Cache for at least 60 seconds; follow Retry-After. Private accounts, paid rankings, and brokerage orders are excluded.",
    "contact": {
      "name": "Loxidev LLC",
      "url": "https://bobby.markets/support",
      "email": "support@loxi.dev"
    },
    "termsOfService": "https://bobby.markets/terms"
  },
  "servers": [
    {
      "url": "https://bobby.markets"
    }
  ],
  "security": [],
  "externalDocs": {
    "description": "MCP and JSON connection guide",
    "url": "https://bobby.markets/agents"
  },
  "paths": {
    "/api/v1/stocks/{symbol}": {
      "get": {
        "operationId": "getPublicStockBrief",
        "summary": "Read one cached public stock or ETF brief",
        "security": [],
        "parameters": [
          {
            "name": "symbol",
            "in": "path",
            "required": true,
            "description": "US stock or ETF ticker; lowercase is accepted and normalized. No additional query parameters.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z][A-Za-z0-9]{0,5}(?:[.-][A-Za-z0-9]{1,2})?$"
            },
            "example": "NVDA"
          }
        ],
        "responses": {
          "200": {
            "description": "Public research; data.status may be partial or unavailable. Unknown symbols and provider outages can produce unavailable data rather than HTTP 404. Quotes may be delayed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StockResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Unknown endpoint",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Read-only; method not allowed",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/compare": {
      "get": {
        "operationId": "compareTwoPublicStocks",
        "summary": "Compare exactly two distinct stocks or ETFs",
        "security": [],
        "parameters": [
          {
            "name": "tickers",
            "in": "query",
            "required": true,
            "description": "Exactly two distinct comma-separated symbols. No additional or repeated query parameters.",
            "schema": {
              "type": "string",
              "maxLength": 19
            },
            "example": "NVDA,MSFT"
          }
        ],
        "responses": {
          "200": {
            "description": "Public research; data.status may be partial or unavailable. Unknown symbols and provider outages can produce unavailable data rather than HTTP 404. Quotes may be delayed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ComparisonResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Unknown endpoint",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Read-only; method not allowed",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/market-pulse": {
      "get": {
        "operationId": "getPublicMarketPulse",
        "summary": "Read the latest cached broad-market pulse",
        "security": [],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Public research; data.status may be partial or unavailable. Unknown symbols and provider outages can produce unavailable data rather than HTTP 404. Quotes may be delayed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PulseResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Unknown endpoint",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Read-only; method not allowed",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Freshness": {
        "type": "object",
        "required": [
          "asOf",
          "ageHours",
          "status"
        ],
        "properties": {
          "asOf": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "ageHours": {
            "type": [
              "number",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "within_24h",
              "older_than_24h",
              "unknown"
            ]
          }
        },
        "additionalProperties": false
      },
      "Quote": {
        "type": "object",
        "required": [
          "price",
          "changePercent",
          "currency",
          "freshness",
          "source",
          "sourceUrl"
        ],
        "properties": {
          "price": {
            "type": [
              "number",
              "null"
            ]
          },
          "changePercent": {
            "type": [
              "number",
              "null"
            ]
          },
          "currency": {
            "type": [
              "string",
              "null"
            ]
          },
          "freshness": {
            "$ref": "#/components/schemas/Freshness"
          },
          "source": {
            "type": "string"
          },
          "sourceUrl": {
            "type": "string",
            "format": "uri"
          }
        },
        "additionalProperties": false
      },
      "News": {
        "type": "object",
        "required": [
          "title",
          "source",
          "url",
          "publishedAt"
        ],
        "properties": {
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "source": {
            "type": [
              "string",
              "null"
            ]
          },
          "url": {
            "type": [
              "string",
              "null"
            ]
          },
          "publishedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "additionalProperties": false
      },
      "Signal": {
        "type": "object",
        "required": [
          "name",
          "direction",
          "observedAt"
        ],
        "properties": {
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "direction": {
            "type": [
              "string",
              "null"
            ]
          },
          "observedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "additionalProperties": false
      },
      "StockBrief": {
        "type": "object",
        "required": [
          "kind",
          "status",
          "ticker",
          "name",
          "quote",
          "history",
          "research",
          "warnings",
          "url"
        ],
        "properties": {
          "kind": {
            "const": "stock_brief"
          },
          "status": {
            "type": "string",
            "enum": [
              "available",
              "partial",
              "unavailable"
            ]
          },
          "ticker": {
            "type": "string"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "quote": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Quote"
              },
              {
                "type": "null"
              }
            ]
          },
          "history": {
            "type": "object",
            "required": [
              "interval",
              "points"
            ],
            "properties": {
              "interval": {
                "const": "1d"
              },
              "points": {
                "type": "array",
                "maxItems": 64,
                "items": {
                  "type": "object",
                  "required": [
                    "at",
                    "close"
                  ],
                  "properties": {
                    "at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "close": {
                      "type": "number"
                    }
                  },
                  "additionalProperties": false
                }
              }
            },
            "additionalProperties": false
          },
          "research": {
            "type": "object",
            "required": [
              "news",
              "signals",
              "source"
            ],
            "properties": {
              "news": {
                "type": "array",
                "maxItems": 4,
                "items": {
                  "$ref": "#/components/schemas/News"
                }
              },
              "signals": {
                "type": "array",
                "maxItems": 3,
                "items": {
                  "$ref": "#/components/schemas/Signal"
                }
              },
              "source": {
                "type": "string"
              }
            },
            "additionalProperties": false
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "url": {
            "type": "string",
            "format": "uri"
          }
        },
        "additionalProperties": false
      },
      "MarketPulse": {
        "type": "object",
        "required": [
          "kind",
          "status",
          "bias",
          "score",
          "conviction",
          "summary",
          "drivers",
          "freshness",
          "warnings",
          "source",
          "url"
        ],
        "properties": {
          "kind": {
            "const": "market_pulse"
          },
          "status": {
            "type": "string",
            "enum": [
              "available",
              "partial",
              "unavailable"
            ]
          },
          "bias": {
            "type": [
              "string",
              "null"
            ]
          },
          "score": {
            "type": [
              "number",
              "null"
            ]
          },
          "conviction": {
            "type": [
              "number",
              "null"
            ],
            "description": "Bobby market-pulse conviction, from 0 to 1 when available."
          },
          "summary": {
            "type": [
              "string",
              "null"
            ]
          },
          "drivers": {
            "type": "array",
            "maxItems": 8,
            "items": {
              "type": "string"
            }
          },
          "freshness": {
            "$ref": "#/components/schemas/Freshness"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "source": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          }
        },
        "additionalProperties": false
      },
      "Comparison": {
        "type": "object",
        "required": [
          "kind",
          "status",
          "rows"
        ],
        "properties": {
          "kind": {
            "const": "stock_comparison"
          },
          "status": {
            "type": "string",
            "enum": [
              "available",
              "partial",
              "unavailable"
            ]
          },
          "rows": {
            "type": "array",
            "minItems": 2,
            "maxItems": 2,
            "items": {
              "$ref": "#/components/schemas/StockBrief"
            }
          }
        },
        "additionalProperties": false
      },
      "StockResponse": {
        "type": "object",
        "required": [
          "apiVersion",
          "generatedAt",
          "data"
        ],
        "properties": {
          "apiVersion": {
            "const": "1"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Response assembly time, NOT the observation time. Check source freshness.asOf and evidence timestamps."
          },
          "data": {
            "$ref": "#/components/schemas/StockBrief"
          }
        },
        "additionalProperties": false
      },
      "PulseResponse": {
        "type": "object",
        "required": [
          "apiVersion",
          "generatedAt",
          "data"
        ],
        "properties": {
          "apiVersion": {
            "const": "1"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Response assembly time, NOT the observation time. Check source freshness.asOf and evidence timestamps."
          },
          "data": {
            "$ref": "#/components/schemas/MarketPulse"
          }
        },
        "additionalProperties": false
      },
      "ComparisonResponse": {
        "type": "object",
        "required": [
          "apiVersion",
          "generatedAt",
          "data"
        ],
        "properties": {
          "apiVersion": {
            "const": "1"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Response assembly time, NOT the observation time. Check source freshness.asOf and evidence timestamps."
          },
          "data": {
            "$ref": "#/components/schemas/Comparison"
          }
        },
        "additionalProperties": false
      },
      "Problem": {
        "type": "object",
        "required": [
          "type",
          "title",
          "status",
          "detail",
          "documentation"
        ],
        "properties": {
          "type": {
            "const": "about:blank"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "documentation": {
            "type": "string",
            "format": "uri"
          }
        },
        "additionalProperties": false
      }
    }
  }
}
