{
  "openapi": "3.1.0",
  "info": {
    "title": "MarsEdge Probability Board API",
    "version": "1.0.0",
    "description": "Read BTC 5-minute model probabilities and Polymarket bid/ask data. Model output is informational and does not guarantee outcomes.",
    "termsOfService": "https://marsedge.vip/api-docs"
  },
  "servers": [
    {
      "url": "https://marsedge.vip",
      "description": "Production"
    }
  ],
  "tags": [
    { "name": "Status", "description": "Public service status" },
    { "name": "API Keys", "description": "API-key verification" },
    { "name": "Probability Board", "description": "Latest and real-time probability data" }
  ],
  "paths": {
    "/api/health": {
      "get": {
        "operationId": "getHealth",
        "summary": "Get service health",
        "tags": ["Status"],
        "responses": {
          "200": {
            "description": "Service status",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/HealthResponse" }
              }
            }
          }
        }
      }
    },
    "/api/keys/verify": {
      "get": {
        "operationId": "verifyApiKey",
        "summary": "Verify an API key",
        "description": "Accepts either X-API-Key or Authorization: Bearer authentication.",
        "tags": ["API Keys"],
        "security": [
          { "ApiKeyAuth": [] },
          { "BearerAuth": [] }
        ],
        "responses": {
          "200": {
            "description": "Valid API key",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/KeyVerificationResponse" }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    },
    "/api/probboard/latest": {
      "get": {
        "operationId": "getLatestProbabilityBoard",
        "summary": "Get the latest probability board",
        "description": "Public guest access is available with rate limits. An optional session or API key selects the account entitlement.",
        "tags": ["Probability Board"],
        "security": [
          {},
          { "ApiKeyAuth": [] },
          { "BearerAuth": [] }
        ],
        "responses": {
          "200": {
            "description": "Latest board",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BoardResponse" }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "headers": {
              "Retry-After": {
                "description": "Seconds until another request should be attempted",
                "schema": { "type": "integer", "minimum": 1 }
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    },
    "/api/probboard/stream": {
      "get": {
        "operationId": "streamProbabilityBoard",
        "summary": "Stream the real-time probability board",
        "description": "VIP Pro Server-Sent Events stream. Board messages use the event name board.",
        "tags": ["Probability Board"],
        "security": [
          { "ApiKeyAuth": [] },
          { "BearerAuth": [] }
        ],
        "responses": {
          "200": {
            "description": "SSE stream",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "Server-Sent Events containing serialized BoardResponse objects"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "403": {
            "description": "VIP Pro entitlement required",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key"
      },
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "MarsEdge API key"
      }
    },
    "schemas": {
      "HealthResponse": {
        "type": "object",
        "required": ["ok", "redisReady", "redisStatus", "refreshMs", "symbols"],
        "properties": {
          "ok": { "type": "boolean", "const": true },
          "redisReady": { "type": "boolean" },
          "redisStatus": { "type": "string" },
          "refreshMs": {
            "type": "object",
            "properties": {
              "guest": { "type": "integer" },
              "user": { "type": "integer" },
              "vip_pro": { "type": "integer" }
            }
          },
          "symbols": {
            "type": "array",
            "items": { "type": "string", "enum": ["btc"] }
          }
        }
      },
      "KeyVerificationResponse": {
        "type": "object",
        "required": ["ok", "userId", "plan"],
        "properties": {
          "ok": { "type": "boolean", "const": true },
          "userId": { "type": "string" },
          "plan": { "type": "string", "examples": ["vip_pro"] },
          "maskedKey": { "type": ["string", "null"] },
          "lastUsedAt": { "type": ["string", "null"], "format": "date-time" }
        }
      },
      "BoardResponse": {
        "type": "object",
        "required": ["ok", "plan", "refreshMs", "items"],
        "properties": {
          "ok": { "type": "boolean", "const": true },
          "plan": { "type": "string", "enum": ["guest", "user", "vip_pro", "admin"] },
          "planLabel": { "type": "string" },
          "refreshMs": { "type": "integer", "minimum": 500 },
          "updatedAt": { "type": ["string", "null"] },
          "servedAt": { "type": "string" },
          "dataAgeMs": { "type": ["number", "null"] },
          "staleThresholdMs": { "type": "number" },
          "severeStaleThresholdMs": { "type": "number" },
          "isStale": { "type": "boolean" },
          "latencyLevel": { "type": "string", "enum": ["ok", "warn", "danger"] },
          "items": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/BoardItem" }
          }
        }
      },
      "BoardItem": {
        "type": "object",
        "required": ["symbol"],
        "properties": {
          "symbol": { "type": "string", "enum": ["BTC"] },
          "slot": { "type": ["number", "null"] },
          "p_up_pct": { "type": ["number", "null"], "minimum": 0, "maximum": 100 },
          "p_down_pct": { "type": ["number", "null"], "minimum": 0, "maximum": 100 },
          "up_bid": { "type": ["number", "null"] },
          "up_ask": { "type": ["number", "null"] },
          "down_bid": { "type": ["number", "null"] },
          "down_ask": { "type": ["number", "null"] },
          "up_token_id": { "type": ["string", "null"] },
          "down_token_id": { "type": ["string", "null"] },
          "rem_secs": { "type": ["number", "null"] },
          "dataAgeMs": { "type": ["number", "null"] },
          "isStale": { "type": "boolean" },
          "latencyLevel": { "type": "string", "enum": ["ok", "warn", "danger"] }
        },
        "additionalProperties": true
      },
      "ErrorResponse": {
        "type": "object",
        "required": ["ok", "error"],
        "properties": {
          "ok": { "type": "boolean", "const": false },
          "code": { "type": "string" },
          "error": { "type": "string" },
          "retryAfterMs": { "type": "integer" }
        }
      }
    }
  }
}
