{
  "openapi": "3.0.3",
  "info": {
    "title": "JustScreener PRO API beta",
    "version": "0.1.0",
    "description": "Read-only PRO beta. Shared API/MCP quota. UTC timestamps. No trading or settings writes."
  },
  "servers": [
    {
      "url": "/api/external/v1"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer"
      }
    },
    "schemas": {
      "Envelope": {
        "type": "object",
        "required": [
          "hasError",
          "response",
          "errors"
        ],
        "properties": {
          "hasError": {
            "type": "boolean"
          },
          "response": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true
          },
          "errors": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "Credits": {
        "type": "object",
        "properties": {
          "used": {
            "type": "integer",
            "minimum": 0
          },
          "limit": {
            "type": "integer",
            "minimum": 0
          },
          "remaining": {
            "type": "integer",
            "minimum": 0
          },
          "resets_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "used",
          "limit",
          "remaining",
          "resets_at"
        ]
      },
      "Access": {
        "type": "object",
        "properties": {
          "beta": {
            "type": "boolean"
          },
          "plan": {
            "type": "string",
            "enum": [
              "PRO"
            ]
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "credits": {
            "$ref": "#/components/schemas/Credits"
          },
          "operations": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "per_minute": {
            "type": "integer",
            "minimum": 0
          }
        },
        "required": [
          "beta",
          "plan",
          "expires_at",
          "credits",
          "operations",
          "per_minute"
        ]
      },
      "MarketSnapshot": {
        "type": "object",
        "properties": {
          "scope": {
            "type": "string",
            "enum": [
              "platform"
            ]
          },
          "timeframe": {
            "type": "string",
            "enum": [
              "day"
            ]
          },
          "sample": {
            "type": "integer",
            "minimum": 0
          },
          "sample_limited": {
            "type": "boolean"
          },
          "oversold_count": {
            "type": "integer",
            "minimum": 0
          },
          "overbought_count": {
            "type": "integer",
            "minimum": 0
          },
          "computed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "source_stale": {
            "type": "boolean"
          },
          "generated_at": {
            "type": "string",
            "format": "date-time"
          },
          "stale": {
            "type": "boolean"
          },
          "source": {
            "type": "string",
            "format": "uri"
          }
        },
        "required": [
          "scope",
          "timeframe",
          "sample",
          "sample_limited",
          "oversold_count",
          "overbought_count",
          "computed_at",
          "source_stale",
          "generated_at",
          "stale",
          "source"
        ]
      },
      "Screeners": {
        "type": "object",
        "properties": {
          "scope": {
            "type": "string",
            "enum": [
              "current_settings"
            ]
          },
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string"
                },
                "active": {
                  "type": "boolean"
                },
                "conditions": {
                  "type": "object",
                  "description": "Structured condition parts and notification rate limit, as currently configured."
                }
              },
              "required": [
                "type",
                "active",
                "conditions"
              ]
            }
          }
        },
        "required": [
          "scope",
          "items"
        ]
      },
      "SignalCard": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "minimum": 1
          },
          "source_symbol": {
            "type": "string"
          },
          "source_bot_type": {
            "type": "string"
          },
          "exchanges": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "changes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_seen_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "repeat_count": {
            "type": "integer",
            "minimum": 0
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "url_scope": {
            "type": "string",
            "enum": [
              "filtered_notification_history"
            ]
          },
          "facts_scope": {
            "type": "string",
            "enum": [
              "latest_episode_update"
            ]
          },
          "fact_price_pct": {
            "type": "number",
            "nullable": true
          },
          "fact_oi_pct": {
            "type": "number",
            "nullable": true
          },
          "fact_volume_pct": {
            "type": "number",
            "nullable": true
          },
          "fact_window_min": {
            "type": "number",
            "nullable": true
          },
          "fact_funding": {
            "type": "number",
            "nullable": true,
            "description": "Funding rate as a fraction (0.0001 means 0.01%), not annualized."
          },
          "fact_oi_usd": {
            "type": "number",
            "nullable": true,
            "description": "Recorded open interest value in USD, when supplied."
          },
          "fact_volume_usd": {
            "type": "number",
            "nullable": true,
            "description": "Recorded turnover in USD, when supplied."
          }
        },
        "required": [
          "id",
          "source_symbol",
          "source_bot_type",
          "exchanges",
          "changes",
          "created_at",
          "last_seen_at",
          "repeat_count",
          "url",
          "url_scope",
          "facts_scope",
          "fact_price_pct",
          "fact_oi_pct",
          "fact_volume_pct",
          "fact_window_min",
          "fact_funding",
          "fact_oi_usd",
          "fact_volume_usd"
        ]
      },
      "SignalPage": {
        "type": "object",
        "properties": {
          "scope": {
            "type": "string",
            "enum": [
              "own_recorded_notifications"
            ]
          },
          "items": {
            "type": "array",
            "maxItems": 25,
            "items": {
              "$ref": "#/components/schemas/SignalCard"
            }
          },
          "next_cursor": {
            "type": "string",
            "nullable": true
          },
          "since": {
            "type": "string",
            "format": "date-time"
          },
          "until": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "scope",
          "items",
          "next_cursor",
          "since",
          "until"
        ]
      },
      "Outcome": {
        "type": "object",
        "properties": {
          "entry_at": {
            "type": "string",
            "format": "date-time"
          },
          "entry_price": {
            "type": "number"
          },
          "state_1h": {
            "type": "string",
            "enum": [
              "pending",
              "missing",
              "available"
            ]
          },
          "move_1h_pct": {
            "type": "number",
            "nullable": true
          },
          "btc_move_1h_pct": {
            "type": "number",
            "nullable": true
          },
          "state_24h": {
            "type": "string",
            "enum": [
              "pending",
              "missing",
              "available"
            ]
          },
          "move_24h_pct": {
            "type": "number",
            "nullable": true
          },
          "btc_move_24h_pct": {
            "type": "number",
            "nullable": true
          }
        },
        "required": [
          "entry_at",
          "entry_price",
          "state_1h",
          "move_1h_pct",
          "btc_move_1h_pct",
          "state_24h",
          "move_24h_pct",
          "btc_move_24h_pct"
        ],
        "nullable": true
      },
      "SignalDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/SignalCard"
          },
          {
            "type": "object",
            "properties": {
              "outcome": {
                "$ref": "#/components/schemas/Outcome"
              },
              "entry_context": {
                "type": "object",
                "additionalProperties": {
                  "type": "number"
                },
                "description": "Allowlisted numeric values from the entry snapshot; may be empty."
              },
              "context_scope": {
                "type": "string",
                "enum": [
                  "entry_snapshot"
                ]
              }
            },
            "required": [
              "outcome",
              "entry_context",
              "context_scope"
            ]
          }
        ]
      },
      "Horizon": {
        "type": "object",
        "properties": {
          "sample": {
            "type": "integer",
            "minimum": 0
          },
          "pending": {
            "type": "integer",
            "minimum": 0
          },
          "missing": {
            "type": "integer",
            "minimum": 0
          },
          "median_move_pct": {
            "type": "number",
            "nullable": true
          },
          "btc_sample": {
            "type": "integer",
            "minimum": 0
          },
          "median_vs_btc_percentage_points": {
            "type": "number",
            "nullable": true
          }
        },
        "required": [
          "sample",
          "pending",
          "missing",
          "median_move_pct",
          "btc_sample",
          "median_vs_btc_percentage_points"
        ]
      },
      "SignalStats": {
        "type": "object",
        "properties": {
          "scope": {
            "type": "string",
            "enum": [
              "own_recorded_outcomes"
            ]
          },
          "window_days": {
            "type": "integer",
            "enum": [
              30
            ]
          },
          "sampled_rows": {
            "type": "integer",
            "minimum": 0,
            "maximum": 2000
          },
          "sample_limited": {
            "type": "boolean"
          },
          "screener_type": {
            "type": "string"
          },
          "horizons": {
            "type": "object",
            "properties": {
              "1h": {
                "$ref": "#/components/schemas/Horizon"
              },
              "24h": {
                "$ref": "#/components/schemas/Horizon"
              }
            },
            "required": [
              "1h",
              "24h"
            ]
          },
          "generated_at": {
            "type": "string",
            "format": "date-time"
          },
          "stale": {
            "type": "boolean"
          }
        },
        "required": [
          "scope",
          "window_days",
          "sampled_rows",
          "sample_limited",
          "screener_type",
          "horizons",
          "generated_at",
          "stale"
        ]
      }
    },
    "responses": {
      "Error401": {
        "description": "Invalid, expired or revoked external key.",
        "headers": {
          "Retry-After": {
            "description": "Seconds before another attempt, when applicable. Do not retry immediately.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Envelope"
            }
          }
        }
      },
      "Error403": {
        "description": "Active verified PRO required.",
        "headers": {
          "Retry-After": {
            "description": "Seconds before another attempt, when applicable. Do not retry immediately.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Envelope"
            }
          }
        }
      },
      "Error404": {
        "description": "Not found; other owners are indistinguishable.",
        "headers": {
          "Retry-After": {
            "description": "Seconds before another attempt, when applicable. Do not retry immediately.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Envelope"
            }
          }
        }
      },
      "Error422": {
        "description": "Invalid arguments.",
        "headers": {
          "Retry-After": {
            "description": "Seconds before another attempt, when applicable. Do not retry immediately.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Envelope"
            }
          }
        }
      },
      "Error429": {
        "description": "Shared account quota or global capacity exhausted. Honor Retry-After.",
        "headers": {
          "Retry-After": {
            "description": "Seconds before another attempt, when applicable. Do not retry immediately.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Envelope"
            }
          }
        }
      },
      "Error503": {
        "description": "Disabled or temporarily unavailable. Honor Retry-After.",
        "headers": {
          "Retry-After": {
            "description": "Seconds before another attempt, when applicable. Do not retry immediately.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Envelope"
            }
          }
        }
      }
    }
  },
  "paths": {
    "/access": {
      "get": {
        "operationId": "get_access",
        "description": "Read access and shared monthly credits. No credits charged.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "response": {
                          "$ref": "#/components/schemas/Access"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "422": {
            "$ref": "#/components/responses/Error422"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        }
      }
    },
    "/market-snapshot": {
      "get": {
        "operationId": "get_market_snapshot",
        "description": "Cached daily-indicator summary. Check computed_at, generated_at and stale.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "response": {
                          "$ref": "#/components/schemas/MarketSnapshot"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "422": {
            "$ref": "#/components/responses/Error422"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        }
      }
    },
    "/screeners": {
      "get": {
        "operationId": "list_my_screeners",
        "description": "Read your current screeners and allowed condition fields.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "response": {
                          "$ref": "#/components/schemas/Screeners"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "422": {
            "$ref": "#/components/responses/Error422"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        }
      }
    },
    "/signals": {
      "get": {
        "operationId": "list_my_signals",
        "description": "Your recorded cards, newest first. Cursor pagination, no total count.",
        "parameters": [
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "until",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "symbol",
            "in": "query",
            "schema": {
              "type": "string",
              "pattern": "^[A-Z0-9]{1,32}$"
            }
          },
          {
            "name": "screener_type",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 220
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 25,
              "default": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "response": {
                          "$ref": "#/components/schemas/SignalPage"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "422": {
            "$ref": "#/components/responses/Error422"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        }
      }
    },
    "/signals/{id}": {
      "get": {
        "operationId": "get_my_signal",
        "description": "Your card, numeric entry context and outcomes. Other owners return NOT_FOUND.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "response": {
                          "$ref": "#/components/schemas/SignalDetail"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "422": {
            "$ref": "#/components/responses/Error422"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        }
      }
    },
    "/signal-stats": {
      "get": {
        "operationId": "get_my_signal_stats",
        "description": "Own 30-day outcomes, latest 2000 sampled records. Separate horizon samples, median movement and BTC difference in percentage points. Not trading profit. Costs 5 credits.",
        "parameters": [
          {
            "name": "screener_type",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "response": {
                          "$ref": "#/components/schemas/SignalStats"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "422": {
            "$ref": "#/components/responses/Error422"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        }
      }
    }
  }
}
