{
  "openapi": "3.1.0",
  "info": {
    "title": "AstralX · World University Trading Competition Rankings Feed",
    "version": "1.0.0-proposed",
    "description": "Provider handoff for AstralX. The host, paths, bearer authentication, errors and closing feed are proposed. Confirm them before implementation. The live success payload matches the current public receiver schema; historical live support requires implementation. Examples are synthetic and are not announcements of official start times."
  },
  "servers": [
    {
      "url": "https://ranking-api.example",
      "description": "Placeholder only. AstralX to supply sandbox and production HTTPS base URLs."
    }
  ],
  "security": [
    {
      "serviceToken": []
    }
  ],
  "paths": {
    "/v1/competitions/{competitionId}/rankings": {
      "get": {
        "operationId": "getLiveRankings",
        "summary": "Get current rankings",
        "description": "Read-only server-to-server full snapshot, not deltas. Each unchanged sequence must return the same content and asOf. Keep the snapshot source timestamp as UTC. Suggested endpoint and authentication.",
        "parameters": [
          {
            "$ref": "#/components/parameters/CompetitionId"
          }
        ],
        "responses": {
          "200": {
            "description": "Complete validated snapshot.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LiveSnapshot"
                },
                "example": {
                  "mode": "live",
                  "asOf": "2026-12-15T12:00:05Z",
                  "sequence": 42,
                  "competitionId": "wutc-sandbox",
                  "currency": "USD",
                  "initialEquity": 10000,
                  "entries": [
                    {
                      "id": "sample-01",
                      "rank": 1,
                      "alias": "Sample Atlas",
                      "countryCode": "GB",
                      "lots": 15.25,
                      "avatar": {
                        "url": null,
                        "consented": false
                      },
                      "strategy": {
                        "status": "pending",
                        "summary": null,
                        "basedOn": null,
                        "asOf": null
                      },
                      "totalReturnPct": 12.5,
                      "equity": 11250,
                      "instruments": [
                        {
                          "symbol": "BTC",
                          "label": {
                            "zh": "比特币",
                            "en": "Bitcoin"
                          },
                          "pnl": 800,
                          "contributionPct": 8.0
                        },
                        {
                          "symbol": "ETH",
                          "label": {
                            "zh": "以太坊",
                            "en": "Ethereum"
                          },
                          "pnl": 450,
                          "contributionPct": 4.5
                        }
                      ],
                      "socials": []
                    },
                    {
                      "id": "sample-02",
                      "rank": 2,
                      "alias": "Sample Birch",
                      "countryCode": null,
                      "lots": 0,
                      "avatar": {
                        "url": null,
                        "consented": false
                      },
                      "strategy": {
                        "status": "pending",
                        "summary": null,
                        "basedOn": null,
                        "asOf": null
                      },
                      "totalReturnPct": 0.0,
                      "equity": 10000,
                      "instruments": [
                        {
                          "symbol": "XAUUSD",
                          "label": {
                            "zh": "黄金 CFD",
                            "en": "Gold CFD"
                          },
                          "pnl": 0,
                          "contributionPct": 0.0
                        }
                      ],
                      "socials": []
                    },
                    {
                      "id": "sample-03",
                      "rank": 3,
                      "alias": "Sample Cedar",
                      "countryCode": "HK",
                      "lots": null,
                      "avatar": {
                        "url": null,
                        "consented": false
                      },
                      "strategy": {
                        "status": "pending",
                        "summary": null,
                        "basedOn": null,
                        "asOf": null
                      },
                      "totalReturnPct": -3.4,
                      "equity": 9660,
                      "instruments": [
                        {
                          "symbol": "ETH",
                          "label": {
                            "zh": "以太坊",
                            "en": "Ethereum"
                          },
                          "pnl": -340,
                          "contributionPct": -3.4
                        }
                      ],
                      "socials": []
                    }
                  ]
                }
              }
            }
          },
          "204": {
            "description": "No eligible live entries yet. No response body."
          },
          "401": {
            "description": "Missing or expired read credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Credential does not have access to this competition.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown competition identifier.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Follow Retry-After when supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds or HTTP-date if available.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "Upstream processing error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Source temporarily unavailable. Retry with bounded backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/competitions/{competitionId}/rankings/previous-24h": {
      "get": {
        "operationId": "getPrevious24hRankings",
        "summary": "Get most recent closed rankings",
        "description": "Proposed separate historical source. Returns the latest complete closed period, stable through the day. Do not generate from the current endpoint. Withhold incomplete or pre-competition intervals unless an opening-period policy is agreed.",
        "parameters": [
          {
            "$ref": "#/components/parameters/CompetitionId"
          }
        ],
        "responses": {
          "200": {
            "description": "Complete validated snapshot.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HistoricalSnapshot"
                },
                "example": {
                  "mode": "live",
                  "periodStart": "2026-12-14T00:00:00.000Z",
                  "periodEnd": "2026-12-15T00:00:00.000Z",
                  "entries": [
                    {
                      "id": "sample-01",
                      "rank": 1,
                      "alias": "Sample Atlas",
                      "totalReturnPct": 9.8
                    },
                    {
                      "id": "sample-02",
                      "rank": 2,
                      "alias": "Sample Birch",
                      "totalReturnPct": 0
                    },
                    {
                      "id": "sample-03",
                      "rank": 3,
                      "alias": "Sample Cedar",
                      "totalReturnPct": -2.15
                    }
                  ]
                }
              }
            }
          },
          "204": {
            "description": "No completed period or no eligible entries. No response body."
          },
          "401": {
            "description": "Missing or expired read credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Credential does not have access to this competition.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown competition identifier.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Follow Retry-After when supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds or HTTP-date if available.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "Upstream processing error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Source temporarily unavailable. Retry with bounded backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "serviceToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "Proposed opaque, read-only server token. Actual auth mechanism to be agreed with AstralX."
      }
    },
    "parameters": {
      "CompetitionId": {
        "name": "competitionId",
        "in": "path",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/PublicIdentifier"
        },
        "description": "AstralX-assigned ID for this competition and environment."
      }
    },
    "schemas": {
      "PublicIdentifier": {
        "type": "string",
        "minLength": 1,
        "maxLength": 80,
        "pattern": "^[a-zA-Z0-9_-]{1,80}$",
        "description": "Stable opaque public ID. Excludes broker account numbers, emails and private identifiers."
      },
      "Timestamp": {
        "type": "string",
        "format": "date-time",
        "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d{1,3})?(?:Z|[+-]\\d{2}:\\d{2})$",
        "description": "RFC 3339 timestamp with explicit offset. Send UTC with Z by preference."
      },
      "Instrument": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "symbol",
          "label",
          "pnl",
          "contributionPct"
        ],
        "properties": {
          "symbol": {
            "enum": [
              "BTC",
              "ETH",
              "XAUUSD",
              "WTI",
              "NAS100",
              "EURUSD"
            ],
            "description": "Current receiver allowlist. Confirm the complete live instrument registry and extend the receiver before launch if needed."
          },
          "label": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "zh",
              "en"
            ],
            "properties": {
              "zh": {
                "type": "string",
                "minLength": 1,
                "maxLength": 80,
                "pattern": "^(?!.*[\\x00-\\x1F\\x7F])(?=.*\\S).+$"
              },
              "en": {
                "type": "string",
                "minLength": 1,
                "maxLength": 80,
                "pattern": "^(?!.*[\\x00-\\x1F\\x7F])(?=.*\\S).+$"
              }
            }
          },
          "pnl": {
            "type": "number",
            "description": "Net P&L by instrument, in USD."
          },
          "contributionPct": {
            "type": "number",
            "description": "Percentage-point contribution to account return, not standalone instrument ROI. pnl / initialEquity × 100."
          }
        }
      },
      "Avatar": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "url",
          "consented"
        ],
        "properties": {
          "url": {
            "oneOf": [
              {
                "type": "string",
                "maxLength": 2048,
                "pattern": "^(?:https://[^/?#@:\\s]+/[^?#\\s]*|/assets/trader-avatars/[A-Za-z0-9][A-Za-z0-9_-]{0,120}\\.(?:png|jpe?g|webp|avif|gif))$",
                "description": "Approved HTTPS public hostname URL or supported same-origin image path. Runtime also checks hostname and decoded path."
              },
              {
                "type": "null"
              }
            ]
          },
          "consented": {
            "type": "boolean"
          }
        },
        "if": {
          "properties": {
            "consented": {
              "const": false
            }
          }
        },
        "then": {
          "properties": {
            "url": {
              "const": null
            }
          }
        }
      },
      "Social": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "platform",
          "url",
          "consented"
        ],
        "properties": {
          "platform": {
            "enum": [
              "x",
              "telegram",
              "youtube",
              "instagram"
            ]
          },
          "url": {
            "oneOf": [
              {
                "type": "string",
                "maxLength": 512,
                "pattern": "^https://[^/?#@\\s]+(?:/[^?#\\s]*)?$",
                "description": "Consent and a platform host allowlist are checked at runtime. Use a verified public profile link."
              },
              {
                "type": "null"
              }
            ]
          },
          "consented": {
            "type": "boolean"
          }
        },
        "if": {
          "properties": {
            "consented": {
              "const": false
            }
          }
        },
        "then": {
          "properties": {
            "url": {
              "const": null
            }
          }
        }
      },
      "Strategy": {
        "oneOf": [
          {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "status",
              "summary",
              "basedOn",
              "asOf"
            ],
            "properties": {
              "status": {
                "const": "pending"
              },
              "summary": {
                "const": null
              },
              "basedOn": {
                "const": null
              },
              "asOf": {
                "const": null
              }
            }
          },
          {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "status",
              "summary",
              "basedOn",
              "asOf"
            ],
            "properties": {
              "status": {
                "const": "ready"
              },
              "summary": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "zh",
                  "en"
                ],
                "properties": {
                  "zh": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 1200,
                    "pattern": "^(?!.*[\\x00-\\x1F\\x7F])(?=.*\\S).+$"
                  },
                  "en": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 1200,
                    "pattern": "^(?!.*[\\x00-\\x1F\\x7F])(?=.*\\S).+$"
                  }
                }
              },
              "basedOn": {
                "const": "trade_records"
              },
              "asOf": {
                "$ref": "#/components/schemas/Timestamp"
              }
            }
          }
        ]
      },
      "LiveEntry": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "rank",
          "alias",
          "countryCode",
          "lots",
          "avatar",
          "strategy",
          "totalReturnPct",
          "equity",
          "instruments",
          "socials"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/PublicIdentifier"
          },
          "rank": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1000,
            "description": "Official ordinal rank. Contiguous, unique and equal to the one-based row position."
          },
          "alias": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80,
            "pattern": "^(?!.*[\\x00-\\x1F\\x7F])(?=.*\\S).+$",
            "description": "Public display alias only. No emails, control characters or angle brackets."
          },
          "countryCode": {
            "oneOf": [
              {
                "enum": [
                  "AD",
                  "AE",
                  "AF",
                  "AG",
                  "AI",
                  "AL",
                  "AM",
                  "AO",
                  "AQ",
                  "AR",
                  "AS",
                  "AT",
                  "AU",
                  "AW",
                  "AX",
                  "AZ",
                  "BA",
                  "BB",
                  "BD",
                  "BE",
                  "BF",
                  "BG",
                  "BH",
                  "BI",
                  "BJ",
                  "BL",
                  "BM",
                  "BN",
                  "BO",
                  "BQ",
                  "BR",
                  "BS",
                  "BT",
                  "BV",
                  "BW",
                  "BY",
                  "BZ",
                  "CA",
                  "CC",
                  "CD",
                  "CF",
                  "CG",
                  "CH",
                  "CI",
                  "CK",
                  "CL",
                  "CM",
                  "CN",
                  "CO",
                  "CR",
                  "CU",
                  "CV",
                  "CW",
                  "CX",
                  "CY",
                  "CZ",
                  "DE",
                  "DJ",
                  "DK",
                  "DM",
                  "DO",
                  "DZ",
                  "EC",
                  "EE",
                  "EG",
                  "EH",
                  "ER",
                  "ES",
                  "ET",
                  "FI",
                  "FJ",
                  "FK",
                  "FM",
                  "FO",
                  "FR",
                  "GA",
                  "GB",
                  "GD",
                  "GE",
                  "GF",
                  "GG",
                  "GH",
                  "GI",
                  "GL",
                  "GM",
                  "GN",
                  "GP",
                  "GQ",
                  "GR",
                  "GS",
                  "GT",
                  "GU",
                  "GW",
                  "GY",
                  "HK",
                  "HM",
                  "HN",
                  "HR",
                  "HT",
                  "HU",
                  "ID",
                  "IE",
                  "IL",
                  "IM",
                  "IN",
                  "IO",
                  "IQ",
                  "IR",
                  "IS",
                  "IT",
                  "JE",
                  "JM",
                  "JO",
                  "JP",
                  "KE",
                  "KG",
                  "KH",
                  "KI",
                  "KM",
                  "KN",
                  "KP",
                  "KR",
                  "KW",
                  "KY",
                  "KZ",
                  "LA",
                  "LB",
                  "LC",
                  "LI",
                  "LK",
                  "LR",
                  "LS",
                  "LT",
                  "LU",
                  "LV",
                  "LY",
                  "MA",
                  "MC",
                  "MD",
                  "ME",
                  "MF",
                  "MG",
                  "MH",
                  "MK",
                  "ML",
                  "MM",
                  "MN",
                  "MO",
                  "MP",
                  "MQ",
                  "MR",
                  "MS",
                  "MT",
                  "MU",
                  "MV",
                  "MW",
                  "MX",
                  "MY",
                  "MZ",
                  "NA",
                  "NC",
                  "NE",
                  "NF",
                  "NG",
                  "NI",
                  "NL",
                  "NO",
                  "NP",
                  "NR",
                  "NU",
                  "NZ",
                  "OM",
                  "PA",
                  "PE",
                  "PF",
                  "PG",
                  "PH",
                  "PK",
                  "PL",
                  "PM",
                  "PN",
                  "PR",
                  "PS",
                  "PT",
                  "PW",
                  "PY",
                  "QA",
                  "RE",
                  "RO",
                  "RS",
                  "RU",
                  "RW",
                  "SA",
                  "SB",
                  "SC",
                  "SD",
                  "SE",
                  "SG",
                  "SH",
                  "SI",
                  "SJ",
                  "SK",
                  "SL",
                  "SM",
                  "SN",
                  "SO",
                  "SR",
                  "SS",
                  "ST",
                  "SV",
                  "SX",
                  "SY",
                  "SZ",
                  "TC",
                  "TD",
                  "TF",
                  "TG",
                  "TH",
                  "TJ",
                  "TK",
                  "TL",
                  "TM",
                  "TN",
                  "TO",
                  "TR",
                  "TT",
                  "TV",
                  "TW",
                  "TZ",
                  "UA",
                  "UG",
                  "UM",
                  "US",
                  "UY",
                  "UZ",
                  "VA",
                  "VC",
                  "VE",
                  "VG",
                  "VI",
                  "VN",
                  "VU",
                  "WF",
                  "WS",
                  "YE",
                  "YT",
                  "ZA",
                  "ZM",
                  "ZW"
                ],
                "description": "Public ISO 3166-1 alpha-2 code. Use GB, not UK."
              },
              {
                "type": "null"
              }
            ]
          },
          "lots": {
            "oneOf": [
              {
                "type": "number",
                "description": "Cumulative standard lots under a jointly confirmed competition-volume definition. Null means unavailable. Zero means known zero.",
                "minimum": 0
              },
              {
                "type": "null"
              }
            ]
          },
          "avatar": {
            "$ref": "#/components/schemas/Avatar"
          },
          "strategy": {
            "$ref": "#/components/schemas/Strategy"
          },
          "totalReturnPct": {
            "type": "number",
            "description": "Account return in percentage units, e.g. 12.5 means +12.5%."
          },
          "equity": {
            "type": "number",
            "description": "Competition account equity in USD."
          },
          "instruments": {
            "type": "array",
            "minItems": 1,
            "maxItems": 6,
            "items": {
              "$ref": "#/components/schemas/Instrument"
            }
          },
          "socials": {
            "type": "array",
            "minItems": 0,
            "maxItems": 4,
            "items": {
              "$ref": "#/components/schemas/Social"
            }
          }
        },
        "description": "No duplicate instrument symbols or social platforms. Required unknown metadata uses null, empty socials, or the pending strategy shape."
      },
      "LiveSnapshot": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "mode",
          "asOf",
          "sequence",
          "competitionId",
          "currency",
          "initialEquity",
          "entries"
        ],
        "properties": {
          "mode": {
            "const": "live"
          },
          "asOf": {
            "$ref": "#/components/schemas/Timestamp"
          },
          "sequence": {
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740991,
            "description": "Monotonic per competition. Retries must preserve timestamp and content for the same sequence."
          },
          "competitionId": {
            "$ref": "#/components/schemas/PublicIdentifier"
          },
          "currency": {
            "const": "USD"
          },
          "initialEquity": {
            "type": "number",
            "description": "Fixed positive starting equity per competition. Current configuration and test fixtures use USD 10000.",
            "exclusiveMinimum": 0
          },
          "entries": {
            "type": "array",
            "minItems": 1,
            "maxItems": 1000,
            "items": {
              "$ref": "#/components/schemas/LiveEntry"
            }
          }
        },
        "description": "Complete replaceable snapshot. Sort by descending total return, with a deterministic official tie-break. Supply top 20 where at least 20 entrants are eligible. Cross-field arithmetic, row uniqueness, time freshness and sequence consistency are verified separately."
      },
      "HistoricalEntry": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "rank",
          "alias",
          "totalReturnPct"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/PublicIdentifier"
          },
          "rank": {
            "type": "integer",
            "minimum": 1,
            "maximum": 20,
            "description": "Official ordinal rank. Contiguous, unique and equal to the one-based row position."
          },
          "alias": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80,
            "pattern": "^(?!.*[\\x00-\\x1F\\x7F])(?=.*\\S).+$",
            "description": "Public display alias only. No emails, control characters or angle brackets."
          },
          "totalReturnPct": {
            "type": "number",
            "description": "Account return in percentage units, e.g. 12.5 means +12.5%."
          }
        }
      },
      "HistoricalSnapshot": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "mode",
          "periodStart",
          "periodEnd",
          "entries"
        ],
        "properties": {
          "mode": {
            "const": "live"
          },
          "periodStart": {
            "$ref": "#/components/schemas/Timestamp"
          },
          "periodEnd": {
            "$ref": "#/components/schemas/Timestamp"
          },
          "entries": {
            "type": "array",
            "minItems": 1,
            "maxItems": 20,
            "items": {
              "$ref": "#/components/schemas/HistoricalEntry"
            }
          }
        },
        "description": "Proposed closing feed. Completed [periodStart, periodEnd) only. Use a 24-hour UTC midnight boundary by default, subject to agreement. Entries are the authoritative closing top 20 or all eligible entrants if fewer, ordered by descending totalReturnPct with official tie-breaks."
      },
      "Error": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "error",
          "message"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        }
      }
    }
  }
}
