{
  "openapi": "3.1.0",
  "info": {
    "title": "Oman Fencing Committee, Public API",
    "version": "1.1.0",
    "description": "Open, unauthenticated JSON endpoints for press, ministries and integrators. CORS *, cache 5–60 minutes.",
    "contact": {
      "name": "OFC",
      "email": "info@fencing.om",
      "url": "https://fencing.om/developers"
    },
    "license": {
      "name": "CC BY 4.0",
      "url": "https://creativecommons.org/licenses/by/4.0/"
    }
  },
  "servers": [
    {
      "url": "https://fencing.om",
      "description": "Production"
    }
  ],
  "paths": {
    "/api/public": {
      "get": {
        "summary": "Endpoint registry",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "Registry"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/athletes": {
      "get": {
        "summary": "List active athletes",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "AthleteList"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/athletes/{slug}": {
      "get": {
        "summary": "Single athlete by slug or id",
        "parameters": [
          {
            "$ref": "#/components/parameters/slug"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "AthleteDetail"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/events": {
      "get": {
        "summary": "List events, newest first",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "EventList"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/events/{id}": {
      "get": {
        "summary": "Single event by id",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "EventDetail"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/upcoming": {
      "get": {
        "summary": "Future events only",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "EventList"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/news": {
      "get": {
        "summary": "List recent news articles",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "NewsList"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/news/{slug}": {
      "get": {
        "summary": "Single news article by slug",
        "parameters": [
          {
            "$ref": "#/components/parameters/slug"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "NewsDetail"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/clubs": {
      "get": {
        "summary": "List member clubs",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "ClubList"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/clubs/{slug}": {
      "get": {
        "summary": "Single club by slug",
        "parameters": [
          {
            "$ref": "#/components/parameters/slug"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "ClubDetail"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/committee": {
      "get": {
        "summary": "Active committee members (NO email/phone)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "CommitteeList"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/committee/{id}": {
      "get": {
        "summary": "Single committee member by id (NO email/phone)",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "CommitteeDetail"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/coaches": {
      "get": {
        "summary": "Active national-programme coaches (NO email/phone)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "CoachList"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/coaches/{id}": {
      "get": {
        "summary": "Single coach by id (NO email/phone). 404 if id is not a coach.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "CoachDetail"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/venues/{slug}": {
      "get": {
        "summary": "Single training venue by slug (kebab-cased clubEn).",
        "parameters": [
          {
            "$ref": "#/components/parameters/slug"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "VenueDetail"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/gallery": {
      "get": {
        "summary": "Gallery photos ordered by display order. Optional ?limit=1-500 (default 200).",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "GalleryList"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/leaderboard": {
      "get": {
        "summary": "Medal leaderboard (gold/silver/bronze per athlete, weighted 3/2/1). Optional ?year=YYYY, ?weapon=foil|epee|sabre, ?limit=1-500.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "Leaderboard"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/stats/clubs": {
      "get": {
        "summary": "Per-club athlete aggregates (total/active + weapon + gender breakdown), sorted by active count.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "ClubStats"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/stats/birthyears": {
      "get": {
        "summary": "Athlete distribution by birth year + FIE age-category counts (U11/U13/U15/U17/U20/Senior).",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "BirthyearStats"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/stats/weapons": {
      "get": {
        "summary": "Per-weapon aggregate (active athletes, gender split, medals tallied from results).",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "WeaponStats"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/venues": {
      "get": {
        "summary": "Training venues for member clubs",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "VenueList"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/results": {
      "get": {
        "summary": "Last 200 tournament results",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "ResultList"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/stats": {
      "get": {
        "summary": "Aggregate counts + breakdowns",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "Stats"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/latest": {
      "get": {
        "summary": "One-call widget feed",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "LatestBundle"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/disciplines": {
      "get": {
        "summary": "Foil/épée/sabre reference data",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "DisciplineList"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/changelog": {
      "get": {
        "summary": "Sprint changelog as JSON",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "ChangelogList"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/feeds.json": {
      "get": {
        "summary": "Catalog of every feed (RSS/Atom/ICS/CSV/JSON)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "FeedCatalog"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/search": {
      "get": {
        "summary": "Cross-entity search (athletes/news/events/clubs/committee)",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 2
            },
            "example": "jana"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 10
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "SearchResults"
                }
              }
            }
          },
          "400": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/search/suggest": {
      "get": {
        "summary": "Lightweight typeahead (max 8 title-only matches)",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 2
            },
            "example": "jan"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "Suggestions"
                }
              }
            }
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "summary": "Uptime probe",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Envelope"
                    }
                  ],
                  "title": "Health"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "slug": {
        "name": "slug",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        },
        "example": "jana-al-sharji"
      },
      "id": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        },
        "example": "west-asia-2025"
      }
    },
    "schemas": {
      "Envelope": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "count": {
            "type": "integer"
          }
        }
      },
      "Health": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "service": {
            "type": "string",
            "example": "fencing-om"
          },
          "now": {
            "type": "string",
            "format": "date-time"
          },
          "uptimeSeconds": {
            "type": "integer"
          },
          "bootAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "entities",
      "description": "Athletes, events, news, clubs, committee, results"
    },
    {
      "name": "aggregates",
      "description": "Stats, latest, upcoming"
    },
    {
      "name": "ops",
      "description": "Health probe"
    }
  ]
}