{
  "openapi": "3.1.0",
  "info": {
    "title": "SignalFloor Public API",
    "version": "1.0.0",
    "description": "Elite-tier REST API for SignalFloor. Bearer-authenticated, rate-limited, JSON responses.\n\nAuth: `Authorization: Bearer sf_<hex>` for live keys or `Bearer sf_sandbox_<hex>` for test-mode keys.\n\nSB-050 versioning: requests should use the `/v1/` prefix. Un-versioned paths are deprecated (sunset 2026-10-22).\n\nSB-051 sandbox keys return deterministic seed data, making them safe for CI fixtures — same request, same bytes, every time.",
    "contact": {
      "name": "SignalFloor Support",
      "url": "https://signalfloor.com/help"
    },
    "license": {
      "name": "Terms of Service",
      "url": "https://signalfloor.com/terms"
    }
  },
  "servers": [
    {
      "url": "https://vszujmlwpywubgzfrxbs.supabase.co/functions/v1/public-api",
      "description": "Production"
    }
  ],
  "tags": [
    { "name": "signals", "description": "Read signal data published by providers." },
    { "name": "providers", "description": "Read provider profiles and performance metrics." }
  ],
  "security": [{ "bearerAuth": [] }],
  "paths": {
    "/v1/signals": {
      "get": {
        "tags": ["signals"],
        "summary": "List recent signals",
        "description": "Paginated list of signals, newest first. All query params are optional.",
        "parameters": [
          { "$ref": "#/components/parameters/ProviderIdFilter" },
          { "name": "asset", "in": "query", "schema": { "type": "string", "example": "EUR/USD" }, "description": "Filter by instrument symbol." },
          { "name": "direction", "in": "query", "schema": { "type": "string", "enum": ["buy", "sell"] } },
          { "name": "status", "in": "query", "schema": { "type": "string", "enum": ["active", "closed", "cancelled", "expired"] } },
          { "name": "since", "in": "query", "schema": { "type": "string", "format": "date-time" }, "description": "ISO timestamp — return signals created at or after this time." },
          { "$ref": "#/components/parameters/Cursor" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": {
            "description": "Signal list",
            "headers": { "$ref": "#/components/headers/StandardSet" },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["data", "pagination"],
                  "properties": {
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/Signal" } },
                    "pagination": { "$ref": "#/components/schemas/Pagination" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/signals/{id}": {
      "get": {
        "tags": ["signals"],
        "summary": "Get signal by ID",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }
        ],
        "responses": {
          "200": {
            "description": "Signal detail",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Signal" } } } } }
          },
          "400": { "description": "Invalid signal ID (must be UUID)" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "description": "Signal not found" }
        }
      }
    },
    "/v1/providers": {
      "get": {
        "tags": ["providers"],
        "summary": "List providers",
        "description": "Paginated provider directory. Sortable by a handful of performance metrics.",
        "parameters": [
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["performance_score", "win_rate", "followers_count", "total_signals", "gross_revenue", "profit_factor"],
              "default": "performance_score"
            }
          },
          { "name": "min_trades", "in": "query", "schema": { "type": "integer", "minimum": 0, "default": 0 }, "description": "Only include providers with at least this many signals." },
          { "name": "verified_only", "in": "query", "schema": { "type": "boolean" } },
          { "$ref": "#/components/parameters/Cursor" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": {
            "description": "Provider list",
            "headers": { "$ref": "#/components/headers/StandardSet" },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["data", "pagination"],
                  "properties": {
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/Provider" } },
                    "pagination": { "$ref": "#/components/schemas/Pagination" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/providers/{id}": {
      "get": {
        "tags": ["providers"],
        "summary": "Get provider by ID",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }
        ],
        "responses": {
          "200": {
            "description": "Provider detail",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Provider" } } } } }
          },
          "400": { "description": "Invalid provider ID (must be UUID)" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "description": "Provider not found" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "sf_<hex> (live) or sf_sandbox_<hex> (test)",
        "description": "Elite subscribers generate keys in /settings → API keys. Any user can mint a sandbox key via the create_public_api_key_sandbox RPC — sandbox keys return deterministic seed data, never hit live tables, and have a 30 req/min cap."
      }
    },
    "parameters": {
      "ProviderIdFilter": {
        "name": "provider_id",
        "in": "query",
        "schema": { "type": "string", "format": "uuid" },
        "description": "Filter to one provider."
      },
      "Cursor": {
        "name": "cursor",
        "in": "query",
        "schema": { "type": "string" },
        "description": "Pagination cursor returned as `pagination.next_cursor` on the previous page."
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 25 }
      }
    },
    "headers": {
      "StandardSet": {
        "description": "Standard response headers attached by the edge function",
        "schema": { "type": "object" }
      },
      "XApiVersion": {
        "description": "Version that served the request (e.g. `v1`).",
        "schema": { "type": "string" }
      },
      "Deprecation": {
        "description": "Present with value `true` when the request hit a deprecated version or un-versioned legacy path.",
        "schema": { "type": "string", "enum": ["true"] }
      },
      "Sunset": {
        "description": "RFC 8594 IMF-fixdate — first moment the deprecated surface may be removed.",
        "schema": { "type": "string" }
      },
      "XRateLimitLimit": {
        "description": "Total requests allowed per minute for this key.",
        "schema": { "type": "integer" }
      },
      "XRateLimitRemaining": {
        "description": "Requests remaining in the current 60-second window.",
        "schema": { "type": "integer" }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid API key",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } }
      },
      "Forbidden": {
        "description": "Key is valid but the caller's Elite subscription has lapsed, or the key lacks the required scope.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } }
      },
      "RateLimited": {
        "description": "Per-key per-minute rate limit exceeded",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } }
      }
    },
    "schemas": {
      "Signal": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "asset": { "type": "string", "example": "EUR/USD" },
          "asset_type": { "type": "string", "enum": ["forex", "crypto", "stocks", "commodities", "indices"] },
          "direction": { "type": "string", "enum": ["buy", "sell"] },
          "entry_price": { "type": "number" },
          "stop_loss": { "type": "number" },
          "take_profit_1": { "type": "number", "nullable": true },
          "take_profit_2": { "type": "number", "nullable": true },
          "take_profit_3": { "type": "number", "nullable": true },
          "status": { "type": "string", "enum": ["active", "closed", "cancelled", "expired"] },
          "result": { "type": "string", "enum": ["win", "loss", "breakeven"], "nullable": true },
          "risk_level": { "type": "string", "enum": ["low", "medium", "high"] },
          "timeframe": { "type": "string", "example": "H4" },
          "analysis": { "type": "string", "nullable": true },
          "provider_id": { "type": "string", "format": "uuid" },
          "created_at": { "type": "string", "format": "date-time" },
          "activated_at": { "type": "string", "format": "date-time", "nullable": true },
          "closed_at": { "type": "string", "format": "date-time", "nullable": true },
          "pnl_percentage": { "type": "number", "nullable": true }
        }
      },
      "Provider": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "name": { "type": "string" },
          "avatar_url": { "type": "string", "nullable": true },
          "description": { "type": "string", "nullable": true },
          "win_rate": { "type": "number", "description": "0–1 win fraction." },
          "total_signals": { "type": "integer" },
          "followers_count": { "type": "integer" },
          "is_verified": { "type": "boolean" },
          "is_premium": { "type": "boolean" },
          "specializations": { "type": "array", "items": { "type": "string" } },
          "provider_tier": { "type": "string", "enum": ["bronze", "silver", "gold"], "nullable": true },
          "performance_score": { "type": "number", "nullable": true },
          "profit_factor": { "type": "number", "nullable": true },
          "max_drawdown": { "type": "number", "nullable": true },
          "early_access_until": { "type": "string", "format": "date-time", "nullable": true },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "limit": { "type": "integer" },
          "next_cursor": { "type": "string", "nullable": true },
          "has_more": { "type": "boolean" }
        }
      },
      "ErrorEnvelope": {
        "type": "object",
        "properties": { "error": { "type": "string" } }
      }
    }
  }
}
