{
  "openapi": "3.1.0",
  "info": {
    "title": "Bizzy Public Station API",
    "version": "1.0.0",
    "description": "Read-only access to Bizzy's full station catalog and public author information. No authentication. No administrative methods, raw stream URLs, playback actions, or private backend documentation. Station data may be cached for five minutes; availability is not a live playback guarantee.",
    "contact": {
      "name": "Misha Naer",
      "email": "hi@mishanaer.com",
      "url": "https://bizzy.fm/contact"
    }
  },
  "servers": [
    {
      "url": "https://bizzy.fm"
    }
  ],
  "security": [],
  "externalDocs": {
    "description": "Bizzy API and MCP documentation",
    "url": "https://bizzy.fm/docs"
  },
  "paths": {
    "/stations/search.json": {
      "get": {
        "operationId": "findStations",
        "summary": "Find Bizzy stations by name or location",
        "description": "Search the full backend catalog, including unavailable stations. Empty query lists all stations. Results are sorted by ID, use next_offset for pagination. Query and pagination are bounded; duplicate parameters are rejected. Unknown query parameters are ignored. No API key required. HEAD returns the same headers without a body.",
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring of station name or location. Empty lists all stations.",
            "schema": {
              "default": "",
              "description": "Case-insensitive substring of station name or location. Empty lists all stations.",
              "type": "string",
              "maxLength": 120
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results, from 1 to 50.",
            "schema": {
              "default": 20,
              "description": "Maximum number of results, from 1 to 50.",
              "type": "integer",
              "minimum": 1,
              "maximum": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of matching stations to skip; results are sorted by ID.",
            "schema": {
              "default": 0,
              "description": "Number of matching stations to skip; results are sorted by ID.",
              "type": "integer",
              "minimum": 0,
              "maximum": 100000
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of matching stations.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StationSearch"
                }
              }
            }
          },
          "400": {
            "description": "Invalid search parameters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Only GET and HEAD are supported.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "406": {
            "description": "Request application/json or */*.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Backend catalog temporarily unavailable; retry after 60 seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          }
        }
      }
    },
    "/stations/{id}.json": {
      "get": {
        "operationId": "getStation",
        "summary": "Read one Bizzy station",
        "description": "Get a station by its real catalog UUID, including stations marked unavailable. Share listen_url with people. No API key required. HEAD is supported.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID from findStations; names are not unique.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "description": "Station UUID from the Bizzy catalog. Never invent an ID."
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Public station card.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Station"
                }
              }
            }
          },
          "404": {
            "description": "Station UUID is invalid or the station does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Only GET and HEAD are supported.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "406": {
            "description": "Request application/json or */*.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Backend catalog temporarily unavailable; retry after 60 seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds before retrying.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          }
        }
      }
    },
    "/about.json": {
      "get": {
        "operationId": "getBizzyInfo",
        "summary": "Read Bizzy authors and contact channels",
        "description": "Public project information, independent of station backend availability. Author contacts do not imply a legal company address or guaranteed support response. No authentication; HEAD is supported.",
        "responses": {
          "200": {
            "description": "Bizzy public author information.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BizzyInfo"
                }
              }
            }
          },
          "405": {
            "description": "Only GET and HEAD are supported.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "406": {
            "description": "Request application/json or */*.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Station": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
            "description": "Station UUID from the Bizzy catalog. Never invent an ID."
          },
          "name": {
            "type": "string",
            "description": "Station name as supplied by the backend."
          },
          "location": {
            "type": "string",
            "description": "Backend location, or an empty string if unspecified."
          },
          "listen_url": {
            "type": "string",
            "format": "uri",
            "description": "Canonical Bizzy listening page to share with a person; not a raw audio stream."
          },
          "availability": {
            "type": "string",
            "enum": [
              "available",
              "unavailable",
              "unknown"
            ],
            "description": "Backend health flag, not a live playback guarantee."
          }
        },
        "required": [
          "id",
          "name",
          "location",
          "listen_url",
          "availability"
        ],
        "additionalProperties": false
      },
      "StationSearch": {
        "type": "object",
        "properties": {
          "stations": {
            "maxItems": 50,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
                  "description": "Station UUID from the Bizzy catalog. Never invent an ID."
                },
                "name": {
                  "type": "string",
                  "description": "Station name as supplied by the backend."
                },
                "location": {
                  "type": "string",
                  "description": "Backend location, or an empty string if unspecified."
                },
                "listen_url": {
                  "type": "string",
                  "format": "uri",
                  "description": "Canonical Bizzy listening page to share with a person; not a raw audio stream."
                },
                "availability": {
                  "type": "string",
                  "enum": [
                    "available",
                    "unavailable",
                    "unknown"
                  ],
                  "description": "Backend health flag, not a live playback guarantee."
                }
              },
              "required": [
                "id",
                "name",
                "location",
                "listen_url",
                "availability"
              ],
              "additionalProperties": false
            }
          },
          "total": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991,
            "description": "Number of matches in the full backend catalog, including unavailable stations."
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50
          },
          "offset": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100000
          },
          "next_offset": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              {
                "type": "null"
              }
            ],
            "description": "Next page offset, or null at the end. Catalog changes can move results between pages."
          }
        },
        "required": [
          "stations",
          "total",
          "limit",
          "offset",
          "next_offset"
        ],
        "additionalProperties": false
      },
      "BizzyInfo": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "const": "Bizzy"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "authors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "url": {
                  "type": "string",
                  "format": "uri"
                }
              },
              "required": [
                "name"
              ],
              "additionalProperties": false
            }
          },
          "contact": {
            "type": "object",
            "properties": {
              "author": {
                "type": "string"
              },
              "email": {
                "type": "string",
                "format": "email",
                "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
              },
              "telegram": {
                "type": "string",
                "format": "uri"
              },
              "x": {
                "type": "string",
                "format": "uri"
              },
              "linkedin": {
                "type": "string",
                "format": "uri"
              }
            },
            "required": [
              "author",
              "email",
              "telegram",
              "x",
              "linkedin"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "name",
          "url",
          "authors",
          "contact"
        ],
        "additionalProperties": false
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "additionalProperties": false,
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          },
          "catalog_url": {
            "type": "string",
            "format": "uri"
          },
          "about_url": {
            "type": "string",
            "format": "uri"
          }
        }
      }
    }
  }
}
