{
  "openapi": "3.1.0",
  "info": {
    "title": "SmackDown On You API",
    "version": "1.0.0",
    "description": "Published wrestling editorial content, profiles, indexed match history, recurring television schedules, dated organizer event listings, promotion coverage, and revocable account access. Event dates are calendar dates and stated event times are not UTC instants. Production requires HTTPS. Cross-origin access is not enabled."
  },
  "servers": [
    {
      "url": "/",
      "description": "Same origin as the magazine"
    }
  ],
  "paths": {
    "/api/v1/promotions": {
      "get": {
        "summary": "List promotion coverage and official calendars",
        "description": "Directory coverage is broader than automatic imports. Check each coverage note; an empty local event list does not mean the promotion has no events. This endpoint is not paginated.",
        "security": [],
        "responses": {
          "200": { "description": "Promotion directory", "content": { "application/json": { "schema": { "type": "object", "required": ["data"], "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Promotion" } } } } } } },
          "429": { "description": "Rate limit exceeded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/v1/events": {
      "get": {
        "summary": "List dated official event listings",
        "description": "Non-unlisted records sorted by event date, title, and ID. Bounds are inclusive calendar dates. Source failure retains last-known listings. Times are organizer labels, not confirmed broadcast times or UTC instants.",
        "security": [],
        "parameters": [
          { "name": "promotion", "in": "query", "description": "Case-insensitive promotion code: wwe, aew, tna, njpw, roh, cmll, or aaa.", "schema": { "type": "string", "examples": ["wwe", "aew", "tna", "njpw", "roh", "cmll", "aaa"] } },
          { "name": "from", "in": "query", "description": "Inclusive organizer calendar date; defaults to today's UTC calendar date.", "schema": { "type": "string", "format": "date" } },
          { "name": "to", "in": "query", "description": "Inclusive upper calendar date; must not precede from. Omit for no upper bound.", "schema": { "type": "string", "format": "date" } },
          { "name": "q", "in": "query", "description": "Title, city or venue search. At most 100 characters are retained.", "schema": { "type": "string", "maxLength": 100 } },
          { "name": "page", "in": "query", "description": "Invalid values use the default.", "schema": { "type": "integer", "minimum": 1, "maximum": 1000000, "default": 1 } },
          { "name": "per_page", "in": "query", "description": "Invalid values use the default.", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 } }
        ],
        "responses": {
          "200": { "description": "Paginated events; empty pages are valid", "content": { "application/json": { "schema": { "type": "object", "required": ["data", "meta"], "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Event" } }, "meta": { "$ref": "#/components/schemas/Pagination" } } } } } },
          "422": { "description": "invalid_filter: unsupported promotion, malformed calendar date, or reversed date range", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "description": "Rate limit exceeded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/v1/events/{slug}": {
      "get": {
        "summary": "Get one event and its source verification state",
        "description": "An existing unlisted record remains available by slug with its status. No match returns 404.",
        "security": [],
        "parameters": [{ "name": "slug", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^[a-z0-9-]+$" } }],
        "responses": {
          "200": { "description": "Event detail", "content": { "application/json": { "schema": { "type": "object", "required": ["data"], "properties": { "data": { "$ref": "#/components/schemas/Event" } } } } } },
          "404": { "description": "Event not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "description": "Rate limit exceeded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/v1/posts": {
      "get": {
        "summary": "List published articles",
        "security": [],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "Invalid values use the default.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000000,
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "description": "Invalid values use the default.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Search article titles and descriptions.",
            "schema": {
              "type": "string",
              "maxLength": 150
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Post"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After is returned in seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/posts/{slug}": {
      "get": {
        "summary": "Get a published article",
        "security": [],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Post"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Published article not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After is returned in seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/categories": {
      "get": {
        "summary": "List editorial categories",
        "security": [],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "Invalid values use the default.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000000,
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "description": "Invalid values use the default.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Category"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After is returned in seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/wrestlers": {
      "get": {
        "summary": "List wrestler profiles",
        "description": "Alphabetical profiles with additive sourced facts and affiliations. Promotion filtering includes historical affiliations, not only current rosters. Page results and metadata are fetched in batches.",
        "security": [],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "Invalid values use the default.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000000,
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "description": "Invalid values use the default.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "promotion",
            "in": "query",
            "description": "Case-insensitive exact promotion name. Matches the legacy profile promotion or any recorded historical/current affiliation; this is a has-wrestled-for filter, not a current-roster filter. Unknown names return an empty collection.",
            "schema": {
              "type": "string",
              "maxLength": 100
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Search profile names, nicknames, slugs, and imported profile data including aliases and facts. Combines with promotion. Percent and underscore are literal characters, not wildcards.",
            "schema": {
              "type": "string",
              "maxLength": 150
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Wrestler"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After is returned in seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/wrestlers/{slug}": {
      "get": {
        "summary": "Get a wrestler profile and indexed match history",
        "description": "Includes a nullable biography containing only the approved, published biography and its sources; private drafts, reviews and generation diagnostics are never returned. Counts include all matches indexed by this site, independent of the requested match page. They are not career totals or official standings. API pagination applies only to matches and defaults to 20 per page; website roster/match pages use 24. An out-of-range match page returns an empty matches array while preserving all-row stats. Zero indexed results means no recorded data, not an undefeated record.",
        "security": [],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$"
            }
          },
          {
            "name": "page",
            "in": "query",
            "description": "Invalid values use the default.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000000,
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "description": "Invalid values use the default.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WrestlerDetail"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Wrestler profile not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After is returned in seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/schedule": {
      "get": {
        "summary": "List recurring broadcast schedules",
        "security": [],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "Invalid values use the default.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000000,
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "description": "Invalid values use the default.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Schedule"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; Retry-After is returned in seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/auth/token": {
      "post": {
        "summary": "Issue a revocable account token",
        "description": "Token lifetime is 30 days. Requires a JSON body. If reCAPTCHA is configured, supply proof for action api_login after explicit consent. A website session is neither required nor created.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TokenRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Token issued; store the returned credential securely.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/TokenResult"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Generic invalid credentials error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request body exceeds 32 KiB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Request requires application/json.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Anti-spam consent or verification required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Authentication rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Revoke the current bearer token",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "204": {
            "description": "Token revoked. Response body is empty."
          },
          "401": {
            "description": "Missing, expired, or revoked bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/me": {
      "get": {
        "summary": "Read the authenticated account",
        "description": "Requires a bearer token. Session cookies do not authorize this endpoint.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/User"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, expired, or revoked bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Opaque random token returned once by POST /api/v1/auth/token. Never send tokens in query strings."
      }
    },
    "schemas": {
      "Promotion": {
        "type": "object",
        "required": ["slug", "name", "full_name", "description", "official_url", "events_url", "coverage", "url", "events_url_local"],
        "properties": {
          "slug": { "type": "string", "enum": ["wwe", "aew", "tna", "njpw", "roh", "cmll", "aaa"] },
          "name": { "type": "string" },
          "full_name": { "type": "string" },
          "description": { "type": "string" },
          "official_url": { "type": "string", "format": "uri" },
          "events_url": { "type": "string", "format": "uri" },
          "coverage": { "type": "string", "description": "Automatic-import scope and limitations; do not infer full calendar coverage." },
          "url": { "type": "string", "format": "uri", "description": "Local editorial category URL." },
          "events_url_local": { "type": "string", "format": "uri", "description": "Local promotion-filtered event URL." }
        }
      },
      "Event": {
        "type": "object",
        "required": ["id", "slug", "promotion", "title", "event_date", "time_label", "time_context", "venue", "city", "status", "source_url", "ticket_url", "last_verified_at", "updated_at", "stale", "url"],
        "properties": {
          "id": { "type": "integer" },
          "slug": { "type": "string" },
          "promotion": { "type": "string", "enum": ["WWE", "AEW", "TNA", "NJPW", "ROH", "CMLL", "AAA"] },
          "title": { "type": "string" },
          "event_date": { "type": "string", "format": "date", "description": "Organizer calendar date, not a UTC instant. No time or timezone is inferred." },
          "time_label": { "type": "string", "description": "Organizer-stated time, including local/doors/taping/TBA context. Do not parse as a confirmed broadcast time or convert to UTC without independent timezone information." },
          "time_context": { "type": "string", "const": "As listed by the organizer; not a confirmed broadcast time." },
          "venue": { "type": "string" },
          "city": { "type": "string", "description": "Empty if not supplied; not inferred from an arena name." },
          "status": { "type": "string", "enum": ["scheduled", "cancelled", "postponed", "unlisted"] },
          "source_url": { "type": "string", "format": "uri", "description": "Official organizer listing." },
          "ticket_url": { "type": "string", "description": "Organizer's linked ticket seller, or an empty string when absent." },
          "last_verified_at": { "type": "string", "format": "date-time", "description": "ISO 8601 UTC timestamp ending in Z, recording when the listing was last seen." },
          "updated_at": { "type": "string", "format": "date-time", "description": "ISO 8601 UTC timestamp ending in Z." },
          "stale": { "type": "boolean", "description": "True when the listing has not been successfully seen in more than three days." },
          "url": { "type": "string", "format": "uri", "description": "Local event-detail URL." }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "Pagination": {
        "type": "object",
        "required": [
          "page",
          "per_page",
          "total",
          "total_pages"
        ],
        "properties": {
          "page": {
            "type": "integer",
            "minimum": 1
          },
          "per_page": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100
          },
          "total": {
            "type": "integer",
            "minimum": 0
          },
          "total_pages": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "Category": {
        "type": "object",
        "required": [
          "id",
          "slug",
          "name",
          "description"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          }
        }
      },
      "Author": {
        "type": "object",
        "required": ["type", "slug", "name", "url", "image_url", "short_bio", "bio", "updated_at"],
        "properties": {
          "type": { "type": "string", "const": "Person" },
          "slug": { "type": "string" },
          "name": { "type": "string" },
          "url": { "type": "string", "format": "uri" },
          "image_url": { "type": "string", "format": "uri" },
          "short_bio": { "type": "string" },
          "bio": { "type": "array", "items": { "type": "string" } },
          "updated_at": { "type": "string", "description": "Stable UTC author-profile update timestamp, separate from the article's dates." }
        }
      },
      "Post": {
        "type": "object",
        "required": [
          "id",
          "slug",
          "title",
          "dek",
          "body_html",
          "image_url",
          "featured",
          "published_at",
          "updated_at",
          "author",
          "category",
          "sources",
          "gallery",
          "url"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "dek": {
            "type": "string"
          },
          "body_html": {
            "type": "string",
            "description": "Editorial HTML; clients should apply their own safe rendering policy."
          },
          "image_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "featured": {
            "type": "boolean"
          },
          "published_at": {
            "type": "string",
            "description": "UTC SQL datetime."
          },
          "updated_at": {
            "type": "string"
          },
          "author": {
            "$ref": "#/components/schemas/Author"
          },
          "category": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer"
              },
              "slug": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "slug",
              "name"
            ]
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "gallery": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "path": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "alt": {
                  "type": "string"
                },
                "caption": {
                  "type": "string"
                }
              }
            }
          },
          "url": {
            "type": "string"
          }
        }
      },
      "Wrestler": {
        "type": "object",
        "description": "Existing v1 fields plus additive imported metadata. Missing facts/aliases/affiliations are empty collections; missing imported-source provenance is null. Import hashes, diagnostics and raw HTML are not exposed.",
        "required": [
          "id",
          "slug",
          "name",
          "promotion",
          "nickname",
          "bio",
          "image_url",
          "image_credit",
          "source_url",
          "updated_at",
          "url"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "promotion": {
            "type": "string",
            "description": "Legacy profile label, which may be historical. Use sourced current_promotions only with its source freshness caveat; this field is not proof of current employment."
          },
          "nickname": {
            "type": "string"
          },
          "bio": {
            "type": "string"
          },
          "image_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Absolute portrait URL when available; JSON null (not an empty string or a generic logo) when no portrait is recorded."
          },
          "image_credit": {
            "type": "object",
            "additionalProperties": true
          },
          "source_url": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "facts": {
            "type": "object",
            "description": "Source-labeled factual strings, such as birthplace, height or debut. Empty object when unavailable; never raw source HTML.",
            "additionalProperties": { "type": "string" }
          },
          "promotions": {
            "type": "array",
            "description": "All recorded historical/current affiliations, combined with the legacy profile label. Not a current roster.",
            "items": { "type": "string" }
          },
          "current_promotions": {
            "type": "array",
            "description": "Affiliations marked current by the source as of the last successful check. An empty array means none recorded, not proof of no affiliation.",
            "items": { "type": "string" }
          },
          "aliases": {
            "type": "array",
            "items": { "type": "string" }
          },
          "source_name": {
            "type": ["string", "null"],
            "description": "Imported metadata provider name, currently WrestlingProfiles. This does not imply an official promotion profile."
          },
          "metadata_source_url": {
            "type": ["string", "null"],
            "format": "uri",
            "description": "Source page for imported facts, separate from the existing source_url profile field."
          },
          "source_checked_at": {
            "type": ["string", "null"],
            "description": "Last successful metadata source check as UTC SQL datetime YYYY-MM-DD HH:MM:SS. Not the source's publication/update date. Failed refreshes retain prior facts and this last-success timestamp."
          }
        }
      },
      "Match": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "slug": {
            "type": "string"
          },
          "match_date": {
            "type": "string",
            "format": "date"
          },
          "event": {
            "type": "string"
          },
          "promotion": {
            "type": "string"
          },
          "opponents": {
            "type": "string"
          },
          "outcome": {
            "type": "string"
          },
          "source_url": {
            "type": "string"
          },
          "result": {
            "type": "string"
          }
        }
      },
      "MatchStats": {
        "type": "object",
        "required": [
          "scope",
          "total",
          "wins",
          "losses",
          "draws",
          "other"
        ],
        "properties": {
          "scope": {
            "type": "string",
            "const": "indexed_matches"
          },
          "total": {
            "type": "integer"
          },
          "wins": {
            "type": "integer"
          },
          "losses": {
            "type": "integer"
          },
          "draws": {
            "type": "integer"
          },
          "other": {
            "type": "integer"
          }
        }
      },
      "WrestlerBiography": {
        "type": "object",
        "description": "Approved, published biography snapshot only. The existing short bio field is unchanged. A later private refresh or review does not replace this snapshot until publication.",
        "additionalProperties": false,
        "required": [
          "body_html",
          "sources",
          "word_count",
          "published_at",
          "updated_at"
        ],
        "properties": {
          "body_html": {
            "type": "string",
            "description": "Published biography HTML with source references. Clients must apply their own safe HTML rendering policy."
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "url",
                "title"
              ],
              "properties": {
                "url": {
                  "type": "string",
                  "format": "uri",
                  "pattern": "^https://",
                  "description": "Public factual reference page, not a license grant."
                },
                "title": {
                  "type": "string"
                }
              }
            }
          },
          "word_count": {
            "type": "integer",
            "minimum": 800,
            "description": "Word count of the published biography."
          },
          "published_at": {
            "type": "string",
            "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$",
            "description": "Biography publication timestamp as a UTC SQL datetime (YYYY-MM-DD HH:MM:SS), not an event date or source publication date."
          },
          "updated_at": {
            "type": "string",
            "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$",
            "description": "Stored biography revision timestamp as a UTC SQL datetime (YYYY-MM-DD HH:MM:SS)."
          }
        }
      },
      "WrestlerDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Wrestler"
          },
          {
            "type": "object",
            "required": [
              "biography",
              "stats",
              "matches",
              "matches_meta"
            ],
            "properties": {
              "biography": {
                "description": "Detail-only published biography; null when no approved published version exists. This field is not included in the wrestler collection response.",
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/WrestlerBiography"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "stats": {
                "$ref": "#/components/schemas/MatchStats"
              },
              "matches": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Match"
                }
              },
              "matches_meta": {
                "$ref": "#/components/schemas/Pagination"
              }
            }
          }
        ]
      },
      "Schedule": {
        "type": "object",
        "required": [
          "id",
          "name",
          "promotion",
          "weekday",
          "local_time",
          "timezone",
          "network",
          "source_url",
          "status",
          "note"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "promotion": {
            "type": "string"
          },
          "weekday": {
            "type": "integer",
            "minimum": 1,
            "maximum": 7,
            "description": "ISO weekday: Monday is 1, Sunday is 7."
          },
          "local_time": {
            "type": "string",
            "description": "Local recurring broadcast time."
          },
          "timezone": {
            "type": "string",
            "description": "IANA timezone, used with the local time to account for daylight saving time."
          },
          "network": {
            "type": "string"
          },
          "source_url": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "note": {
            "type": "string"
          }
        }
      },
      "User": {
        "type": "object",
        "required": [
          "id",
          "name",
          "email",
          "role",
          "email_verified",
          "ad_free",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "role": {
            "type": "string",
            "enum": [
              "reader",
              "admin"
            ]
          },
          "email_verified": {
            "type": "boolean"
          },
          "ad_free": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string"
          }
        }
      },
      "TokenRequest": {
        "type": "object",
        "required": [
          "email",
          "password"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 254
          },
          "password": {
            "type": "string",
            "format": "password",
            "maxLength": 72,
            "writeOnly": true,
            "description": "Actual server limit is 72 bytes."
          },
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "recaptcha_consent": {
            "type": "string",
            "const": "1",
            "description": "Required with reCAPTCHA enabled. Explicit consent for this submission."
          },
          "recaptcha_token": {
            "type": "string",
            "maxLength": 8192,
            "writeOnly": true,
            "description": "Required with reCAPTCHA enabled. Expected action: api_login."
          }
        }
      },
      "TokenResult": {
        "type": "object",
        "required": [
          "token_type",
          "access_token",
          "expires_at",
          "user"
        ],
        "properties": {
          "token_type": {
            "type": "string",
            "const": "Bearer"
          },
          "access_token": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{43}$",
            "description": "Shown once. Persist only in a secure credential store."
          },
          "expires_at": {
            "type": "string",
            "description": "UTC expiration timestamp."
          },
          "user": {
            "$ref": "#/components/schemas/User"
          }
        }
      }
    }
  }
}
