{
  "openapi": "3.1.0",
  "info": {
    "title": "AniVibe Partner API",
    "version": "1.0.0",
    "description": "Public catalogue API and signed AniVibe player embeds. Metadata never contains upstream MP4/CDN URLs."
  },
  "servers": [
    {"url": "https://anivibe.pro", "description": "Production"},
    {"url": "http://localhost", "description": "Local XAMPP"}
  ],
  "tags": [
    {"name": "Catalogue"},
    {"name": "Embed"},
    {"name": "Partners"}
  ],
  "paths": {
    "/api/v1/search": {
      "get": {
        "tags": ["Catalogue"],
        "summary": "Search by title or Shikimori ID",
        "parameters": [
          {"name": "q", "in": "query", "required": true, "schema": {"type": "string", "minLength": 2}},
          {"name": "limit", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 50, "default": 20}}
        ],
        "responses": {
          "200": {"description": "Anime list"},
          "429": {"$ref": "#/components/responses/RateLimited"}
        }
      }
    },
    "/api/v1/recent": {
      "get": {
        "tags": ["Catalogue"],
        "summary": "Recently updated anime",
        "parameters": [
          {"name": "limit", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 50, "default": 20}},
          {"name": "cursor", "in": "query", "schema": {"type": "string"}}
        ],
        "responses": {"200": {"description": "Cursor-paginated anime list"}}
      }
    },
    "/api/v1/anime/{shikimori_id}": {
      "get": {
        "tags": ["Catalogue"],
        "summary": "Anime details",
        "parameters": [{"$ref": "#/components/parameters/ShikimoriId"}],
        "responses": {
          "200": {"description": "Anime metadata"},
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      }
    },
    "/api/v1/anime/{shikimori_id}/episodes": {
      "get": {
        "tags": ["Catalogue"],
        "summary": "Episode and translation metadata",
        "parameters": [
          {"$ref": "#/components/parameters/ShikimoriId"},
          {"name": "from", "in": "query", "schema": {"type": "integer", "minimum": 1, "default": 1}},
          {"name": "limit", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 100, "default": 25}}
        ],
        "responses": {"200": {"description": "Episodes without source URLs"}}
      }
    },
    "/api/v1/partners/register": {
      "post": {
        "tags": ["Partners"],
        "summary": "Register a standard partner",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {"schema": {"$ref": "#/components/schemas/PartnerRegistration"}}
          }
        },
        "responses": {
          "201": {"description": "Partner created; the API key is returned once"},
          "409": {"description": "Origin already registered"},
          "429": {"$ref": "#/components/responses/RateLimited"}
        }
      }
    },
    "/api/v1/embed-token": {
      "post": {
        "tags": ["Embed"],
        "summary": "Create a signed player iframe URL",
        "security": [{"ApiKey": []}],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {"schema": {"$ref": "#/components/schemas/EmbedRequest"}}
          }
        },
        "responses": {
          "201": {"description": "Temporary embed URL"},
          "401": {"$ref": "#/components/responses/Unauthorized"},
          "403": {"description": "Partner or origin blocked"},
          "429": {"$ref": "#/components/responses/RateLimited"}
        }
      }
    },
    "/api/v1/partner/me": {
      "get": {
        "tags": ["Partners"],
        "summary": "Profile, domains, key masks and limits",
        "security": [{"ApiKey": []}],
        "responses": {
          "200": {"description": "Partner profile"},
          "401": {"$ref": "#/components/responses/Unauthorized"}
        }
      }
    },
    "/api/v1/partner/stats": {
      "get": {
        "tags": ["Partners"],
        "summary": "Daily usage, playback, traffic and ad statistics",
        "security": [{"ApiKey": []}],
        "parameters": [
          {"name": "days", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 90, "default": 30}}
        ],
        "responses": {
          "200": {"description": "Daily partner statistics"},
          "401": {"$ref": "#/components/responses/Unauthorized"}
        }
      }
    },
    "/api/v1/partner/limit-request": {
      "post": {
        "tags": ["Partners"],
        "summary": "Request higher limits",
        "security": [{"ApiKey": []}],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {"schema": {"$ref": "#/components/schemas/LimitRequest"}}
          }
        },
        "responses": {
          "201": {"description": "Request queued"},
          "409": {"description": "A request is already pending"}
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "Keep the key on the partner server."
      }
    },
    "parameters": {
      "ShikimoriId": {
        "name": "shikimori_id",
        "in": "path",
        "required": true,
        "schema": {"type": "integer", "minimum": 1}
      }
    },
    "schemas": {
      "PartnerRegistration": {
        "type": "object",
        "required": ["name", "email", "origin"],
        "properties": {
          "name": {"type": "string", "minLength": 2, "maxLength": 120},
          "email": {"type": "string", "format": "email"},
          "origin": {"type": "string", "format": "uri", "examples": ["https://example.com"]}
        }
      },
      "EmbedRequest": {
        "type": "object",
        "required": ["anime_id", "origin"],
        "properties": {
          "anime_id": {"type": "integer", "minimum": 1},
          "origin": {"type": "string", "format": "uri"},
          "episode": {"type": "integer", "minimum": 1},
          "translation": {"type": "string"},
          "ttl": {"type": "integer", "minimum": 300, "maximum": 7200}
        }
      },
      "LimitRequest": {
        "type": "object",
        "required": ["reason"],
        "properties": {
          "read_rpm": {"type": "integer"},
          "embed_rpm": {"type": "integer"},
          "daily_embed_limit": {"type": "integer"},
          "reason": {"type": "string", "minLength": 20}
        }
      },
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {"type": "string"},
          "message": {"type": "string"}
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Invalid, revoked or blocked API key",
        "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}
      },
      "NotFound": {
        "description": "Resource not found",
        "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}
      },
      "RateLimited": {
        "description": "Minute or daily quota exceeded",
        "headers": {
          "X-RateLimit-Limit": {"schema": {"type": "integer"}},
          "X-RateLimit-Remaining": {"schema": {"type": "integer"}},
          "X-RateLimit-Reset": {"schema": {"type": "integer"}}
        },
        "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}
      }
    }
  }
}
