{
  "openapi": "3.1.0",
  "info": {
    "title": "Tradee Generated Output Feeds v1",
    "version": "1.0.0",
    "description": "Execution-scoped modeled-position event feeds. The token is an opaque bearer embedded in the path."
  },
  "servers": [{"url": "https://tradee.bot/app"}],
  "paths": {
    "/api/v1/output/feeds/{token}": {
      "get": {
        "operationId": "getOutputFeed",
        "summary": "Get feed metadata and canonical links",
        "parameters": [{"$ref": "#/components/parameters/FeedToken"}],
        "responses": {
          "200": {
            "description": "Feed metadata and canonical token-path links",
            "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MetadataResponse"}}}
          },
          "404": {"$ref": "#/components/responses/InvalidFeed"},
          "429": {"$ref": "#/components/responses/RateLimited"},
          "503": {"$ref": "#/components/responses/FeedConfigurationError"}
        }
      }
    },
    "/api/v1/output/feeds/{token}/stream": {
      "get": {
        "operationId": "streamOutputFeed",
        "summary": "Stream future events and cursor-based reconnects over SSE",
        "parameters": [
          {"$ref": "#/components/parameters/FeedToken"},
          {"name": "Last-Event-ID", "in": "header", "required": false, "schema": {"type": "string"}},
          {"name": "after", "in": "query", "required": false, "schema": {"type": "string"}}
        ],
        "responses": {
          "200": {
            "description": "SSE frames. A new stream starts with ready; modeled frames use the topic as event and an opaque post-event cursor as id.",
            "content": {"text/event-stream": {"schema": {"type": "string"}}}
          },
          "400": {"$ref": "#/components/responses/InvalidCursor"},
          "404": {"$ref": "#/components/responses/InvalidFeed"},
          "429": {"$ref": "#/components/responses/RateLimited"},
          "503": {"$ref": "#/components/responses/FeedConfigurationError"}
        }
      }
    },
    "/api/v1/output/feeds/{token}/snapshot": {
      "get": {
        "operationId": "getOutputSnapshot",
        "summary": "Get active modeled positions and an atomic event cursor",
        "parameters": [{"$ref": "#/components/parameters/FeedToken"}],
        "responses": {
          "200": {
            "description": "Repeatable-read snapshot",
            "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SnapshotResponse"}}}
          },
          "404": {"$ref": "#/components/responses/InvalidFeed"},
          "429": {"$ref": "#/components/responses/RateLimited"}
        }
      }
    },
    "/api/v1/output/feeds/{token}/events": {
      "get": {
        "operationId": "listOutputEvents",
        "summary": "List retained events after an opaque feed cursor",
        "parameters": [
          {"$ref": "#/components/parameters/FeedToken"},
          {"name": "after", "in": "query", "required": false, "schema": {"type": "string"}},
          {"name": "limit", "in": "query", "required": false, "description": "Requested page size; the service clamps it to 1-500.", "schema": {"type": "integer", "minimum": 1, "maximum": 500, "default": 100}}
        ],
        "responses": {
          "200": {
            "description": "Events in durable cursor order",
            "content": {"application/json": {"schema": {"$ref": "#/components/schemas/EventPage"}}}
          },
          "400": {"$ref": "#/components/responses/InvalidCursor"},
          "404": {"$ref": "#/components/responses/InvalidFeed"},
          "429": {"$ref": "#/components/responses/RateLimited"}
        }
      }
    }
  },
  "components": {
    "parameters": {
      "FeedToken": {
        "name": "token",
        "in": "path",
        "required": true,
        "description": "Opaque path bearer. Keep the token and complete URL out of logs, analytics, history, and support captures.",
        "schema": {"type": "string", "pattern": "^trd_feed_", "minLength": 49, "maxLength": 128}
      }
    },
    "responses": {
      "InvalidFeed": {
        "description": "Missing, revoked, or invalid feed",
        "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}, "example": {"error": "invalid_feed"}}}
      },
      "InvalidCursor": {
        "description": "Malformed, modified, wrong-feed, or wrong-execution cursor",
        "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}, "example": {"error": "invalid_cursor"}}}
      },
      "RateLimited": {
        "description": "More than 300 requests in the feed's 60-second window",
        "headers": {"Retry-After": {"schema": {"type": "string"}, "example": "60"}},
        "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}, "example": {"error": "rate_limit_exceeded"}}}
      },
      "FeedConfigurationError": {
        "description": "Trusted public URL or cursor-signing configuration is unavailable",
        "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}, "example": {"error": "feed_configuration_error"}}}
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "additionalProperties": false,
        "required": ["error"],
        "properties": {"error": {"type": "string"}}
      },
      "DecimalString": {"type": "string", "pattern": "^-?[0-9]+(?:\\.[0-9]+)?$"},
      "NullableDecimalString": {
        "anyOf": [
          {"$ref": "#/components/schemas/DecimalString"},
          {"type": "null"}
        ]
      },
      "FeedLinks": {
        "type": "object",
        "additionalProperties": false,
        "required": ["metadata", "stream", "snapshot", "events"],
        "properties": {
          "metadata": {"type": "string", "format": "uri"},
          "stream": {"type": "string", "format": "uri"},
          "snapshot": {"type": "string", "format": "uri"},
          "events": {"type": "string", "format": "uri"}
        }
      },
      "MetadataResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": ["feed_id", "execution_id", "schema_version", "links"],
        "properties": {
          "feed_id": {"type": "integer"},
          "execution_id": {"type": "integer"},
          "schema_version": {"const": 1},
          "links": {"$ref": "#/components/schemas/FeedLinks"}
        }
      },
      "ReadyEventData": {
        "type": "object",
        "additionalProperties": false,
        "required": ["snapshot", "events", "starting_cursor"],
        "properties": {
          "snapshot": {"type": "string", "format": "uri"},
          "events": {"type": "string", "format": "uri"},
          "starting_cursor": {"type": "string"}
        },
        "description": "JSON encoded in the data field of the initial SSE ready frame. The SSE id equals starting_cursor."
      },
      "Reference": {
        "type": "object",
        "additionalProperties": false,
        "required": ["venue", "instrument", "asset_type"],
        "properties": {
          "venue": {"type": "string"},
          "instrument": {"type": "string"},
          "asset_type": {"type": "string"},
          "timeframe": {"type": "string"}
        }
      },
      "ModeledPosition": {
        "type": "object",
        "additionalProperties": false,
        "required": ["id", "revision", "execution_id", "bot_id", "instance_id", "reference", "direction", "entry_price", "initial_quantity", "quantity_remaining", "entry_time", "stop_loss", "take_profit", "trailing_enabled", "updated_at"],
        "properties": {
          "id": {"type": "integer"},
          "revision": {"type": "integer", "minimum": 1},
          "execution_id": {"type": "integer"},
          "bot_id": {"type": "integer"},
          "instance_id": {"type": "string"},
          "reference": {"$ref": "#/components/schemas/Reference"},
          "direction": {"enum": ["long", "short"]},
          "entry_price": {"$ref": "#/components/schemas/DecimalString"},
          "initial_quantity": {"$ref": "#/components/schemas/DecimalString"},
          "quantity_remaining": {"$ref": "#/components/schemas/DecimalString"},
          "entry_time": {"type": "string", "format": "date-time"},
          "stop_loss": {"$ref": "#/components/schemas/NullableDecimalString"},
          "take_profit": {"$ref": "#/components/schemas/NullableDecimalString"},
          "trailing_enabled": {"type": "boolean"},
          "updated_at": {"type": "string", "format": "date-time"}
        }
      },
      "ModeledPositionEvent": {
        "type": "object",
        "additionalProperties": false,
        "required": ["event_id", "schema_version", "topic", "type", "occurred_at", "actionable_until", "execution_id", "bot_id", "bot_version", "instance_id", "reference", "transition", "position", "sizing"],
        "properties": {
          "event_id": {"type": "string", "format": "uuid"},
          "schema_version": {"const": 1},
          "topic": {"enum": ["modeled_position.opened", "modeled_position.updated", "modeled_position.reduced", "modeled_position.closed"]},
          "type": {"enum": ["modeled_position.opened", "modeled_position.updated", "modeled_position.reduced", "modeled_position.closed"]},
          "occurred_at": {"type": "string", "format": "date-time"},
          "actionable_until": {"type": "string", "format": "date-time"},
          "execution_id": {"type": "integer"},
          "bot_id": {"type": "integer"},
          "bot_version": {"type": "integer"},
          "instance_id": {"type": "string"},
          "reference": {
            "allOf": [{"$ref": "#/components/schemas/Reference"}],
            "required": ["venue", "instrument", "asset_type", "timeframe"]
          },
          "transition": {
            "type": "object",
            "additionalProperties": false,
            "required": ["quantity_delta", "reason", "reference_price"],
            "properties": {
              "quantity_delta": {"$ref": "#/components/schemas/NullableDecimalString"},
              "reason": {"type": "string"},
              "reference_price": {"$ref": "#/components/schemas/NullableDecimalString"}
            }
          },
          "position": {
            "type": "object",
            "additionalProperties": false,
            "required": ["id", "revision", "direction", "entry_price", "initial_quantity", "quantity_remaining", "entry_time", "stop_loss", "take_profit", "trailing_enabled"],
            "properties": {
              "id": {"type": "integer"},
              "revision": {"type": "integer", "minimum": 1},
              "direction": {"enum": ["long", "short"]},
              "entry_price": {"$ref": "#/components/schemas/DecimalString"},
              "initial_quantity": {"$ref": "#/components/schemas/DecimalString"},
              "quantity_remaining": {"$ref": "#/components/schemas/DecimalString"},
              "entry_time": {"type": "string", "format": "date-time"},
              "stop_loss": {"$ref": "#/components/schemas/NullableDecimalString"},
              "take_profit": {"$ref": "#/components/schemas/NullableDecimalString"},
              "trailing_enabled": {"type": "boolean"}
            }
          },
          "sizing": {
            "type": "object",
            "additionalProperties": false,
            "required": ["modeled_slice", "configured", "risk_fallback_used"],
            "properties": {
              "modeled_slice": {"$ref": "#/components/schemas/NullableDecimalString"},
              "configured": {
                "type": "object",
                "additionalProperties": false,
                "required": ["type", "value", "fallback"],
                "properties": {
                  "type": {"type": "string"},
                  "value": {"$ref": "#/components/schemas/NullableDecimalString"},
                  "fallback": {
                    "anyOf": [
                      {
                        "type": "object",
                        "additionalProperties": false,
                        "required": ["type", "value"],
                        "properties": {
                          "type": {"type": "string"},
                          "value": {"$ref": "#/components/schemas/NullableDecimalString"}
                        }
                      },
                      {"type": "null"}
                    ]
                  }
                }
              },
              "risk_fallback_used": {"type": "boolean"}
            }
          }
        }
      },
      "SnapshotResponse": {
        "type": "object",
        "required": ["positions", "cursor"],
        "properties": {
          "positions": {"type": "array", "items": {"$ref": "#/components/schemas/ModeledPosition"}},
          "cursor": {"type": "string"}
        }
      },
      "EventPage": {
        "type": "object",
        "required": ["events", "cursor", "has_more"],
        "properties": {
          "events": {"type": "array", "items": {"$ref": "#/components/schemas/ModeledPositionEvent"}},
          "cursor": {"type": "string"},
          "has_more": {"type": "boolean"}
        }
      }
    }
  }
}
