{
  "openapi": "3.1.0",
  "info": {
    "title": "MadamNazar.io API",
    "version": "1.0.0",
    "summary": "Madam Nazar's daily location and Red Dead Online daily data (challenges, weekly set, jewelry, events), as JSON.",
    "description": "Madam Nazar, the collector in Red Dead Online, moves every day at 06:00 UTC. This API returns where she is today and where she was on past days (the same data that powers https://madamnazar.io), plus the day's challenges, her collection set of the week, lost jewelry in static spots and the free roam event schedule.\n\nEvery request needs an API key (`mn_live_…`) in the `x-api-key` header. Keys come from a madamnazar.io account (https://madamnazar.io/account#api): free (today's location only, 100 requests per UTC day) or Developer (€4.99/month or €39/year, every endpoint, 1,000 requests per UTC day).\n\nEvery response, errors included, is JSON and ends with a `meta` object (request id, game day, next reset, quota). Errors carry a stable `error` code and a readable `message`. The location changes once a day: cache responses for `meta.cache_ttl_seconds`.\n\nWherever you show the data, display the `attribution` text with a link to madamnazar.io.",
    "termsOfService": "https://developer.madamnazar.io/",
    "contact": {
      "name": "MadamNazar.io",
      "url": "https://madamnazar.io/contact",
      "email": "contact@lucasbonomi.com"
    }
  },
  "externalDocs": {
    "description": "Developer documentation, pricing and key management",
    "url": "https://developer.madamnazar.io/"
  },
  "servers": [
    { "url": "https://developer.madamnazar.io", "description": "Production" }
  ],
  "security": [{ "ApiKeyHeader": [] }],
  "tags": [
    { "name": "location", "description": "Madam Nazar's daily location" },
    { "name": "daily", "description": "Daily challenges and free roam events" },
    { "name": "collector", "description": "Collector role: weekly set and lost jewelry" }
  ],
  "paths": {
    "/v1/location/today": {
      "get": {
        "operationId": "getTodayLocation",
        "tags": ["location"],
        "summary": "Get Madam Nazar's location today",
        "description": "Returns today's location (the game day that started at the last 06:00 UTC reset). Just after the reset, before today's location is confirmed, answers 503 `location_not_confirmed_yet` with a `Retry-After` header instead of returning yesterday's spot.",
        "responses": {
          "200": {
            "description": "Today's location.",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" },
              "X-Request-Id": { "$ref": "#/components/headers/X-Request-Id" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/LocationResponse" },
                "example": {
                  "date": "2026-10-26",
                  "location": {
                    "name": "point 8",
                    "region": "west elizabeth",
                    "cardinals": "south east",
                    "image": "https://madamnazar.github.io/images/location/rdo_map_nazar_8_btvqxe.jpg"
                  },
                  "attribution": "Data by MadamNazar.io — https://madamnazar.io",
                  "meta": {
                    "api_version": "v1",
                    "request_id": "01M5T8GK73WBWWM2C9JDRT08V1",
                    "requested_at": "2026-10-26T09:12:04.318Z",
                    "response_time_ms": 41,
                    "game_day": "2026-10-26",
                    "next_reset_at": "2026-10-27T06:00:00.000Z",
                    "valid_from": "2026-10-26T06:00:00.000Z",
                    "valid_until": "2026-10-27T06:00:00.000Z",
                    "location_updated_at": "2026-10-26T06:03:11.000Z",
                    "cache_ttl_seconds": 60,
                    "rate_limit": { "limit": 1000, "remaining": 999, "resets_at": "2026-10-27T00:00:00.000Z" },
                    "docs": "https://developer.madamnazar.io"
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "503": { "$ref": "#/components/responses/Unavailable" },
          "default": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/location/history": {
      "get": {
        "operationId": "getLocationHistory",
        "tags": ["location"],
        "summary": "Get past locations",
        "description": "Without `date`: the last `days` game days, newest first (today included once confirmed). With `date`: the location of that single game day.",
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "description": "Number of game days to return, newest first. Ignored when `date` is set.",
            "required": false,
            "schema": { "type": "integer", "minimum": 1, "maximum": 30, "default": 7 }
          },
          {
            "name": "date",
            "in": "query",
            "description": "A single game day (YYYY-MM-DD). Returns one location instead of a list.",
            "required": false,
            "schema": { "type": "string", "format": "date", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" }
          }
        ],
        "responses": {
          "200": {
            "description": "A list of locations (`days`), or one location (`date`).",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" },
              "X-Request-Id": { "$ref": "#/components/headers/X-Request-Id" }
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    { "$ref": "#/components/schemas/HistoryResponse" },
                    { "$ref": "#/components/schemas/LocationResponse" }
                  ]
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "default": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/challenges/today": {
      "get": {
        "operationId": "getTodayChallenges",
        "tags": ["daily"],
        "summary": "Get today's daily challenges",
        "description": "The 7 general daily challenges and the role challenges (easy, medium, hard) of the current game day. Answers 503 `not_published_yet` just after the 06:00 UTC reset until they are out.",
        "responses": {
          "200": {
            "description": "Get today's daily challenges.",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" },
              "X-Request-Id": { "$ref": "#/components/headers/X-Request-Id" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ChallengesResponse" },
                "example": {
                  "date": "2026-10-27",
                  "general": {
                    "role": "general",
                    "starts_at": "2026-10-27T06:00:00.000Z",
                    "ends_at": "2026-10-28T06:00:00.000Z",
                    "challenges": [
                      { "id": "MPDC_AnimalsSkinned_Muskrat_00", "goal": 2, "description": "American Muskrats skinned" }
                    ]
                  },
                  "roles": {
                    "easy": [
                      {
                        "role": "bounty_hunter",
                        "starts_at": "2026-10-27T06:00:00.000Z",
                        "ends_at": "2026-10-28T06:00:00.000Z",
                        "challenges": [
                          {
                            "id": "MPRC_Bounty_General_Delivered_Easy_00",
                            "goal": 1,
                            "description": "Bounty targets brought in"
                          }
                        ]
                      }
                    ],
                    "medium": [],
                    "hard": []
                  },
                  "source": { "name": "RDO Dailies", "url": "https://rdo-dailies.com" },
                  "attribution": "Data by MadamNazar.io — https://madamnazar.io",
                  "meta": {
                    "api_version": "v1",
                    "request_id": "01M5TA0…",
                    "requested_at": "2026-10-27T09:12:04.318Z",
                    "response_time_ms": 44,
                    "game_day": "2026-10-27",
                    "next_reset_at": "2026-10-28T06:00:00.000Z",
                    "valid_from": "2026-10-27T06:00:00.000Z",
                    "valid_until": "2026-10-28T06:00:00.000Z",
                    "data_updated_at": "2026-10-27T06:09:02.000Z",
                    "cache_ttl_seconds": 60,
                    "rate_limit": { "limit": 1000, "remaining": 997, "resets_at": "2026-10-28T00:00:00.000Z" },
                    "docs": "https://developer.madamnazar.io"
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "503": { "$ref": "#/components/responses/Unavailable" },
          "default": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/collector/weekly-set": {
      "get": {
        "operationId": "getWeeklySet",
        "tags": ["collector"],
        "summary": "Get Madam Nazar's weekly collection set",
        "description": "The collection Madam Nazar asks for this week, with its items and the dollars she pays for it (`value`).",
        "responses": {
          "200": {
            "description": "Get Madam Nazar's weekly collection set.",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" },
              "X-Request-Id": { "$ref": "#/components/headers/X-Request-Id" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/WeeklySetResponse" },
                "example": {
                  "date": "2026-10-27",
                  "set": {
                    "id": "set_saint_denis",
                    "name": "Saint Denis",
                    "value": 85,
                    "items": [
                      { "id": "consumable_cognac", "name": "Cognac Bottle" },
                      { "id": "provision_jewelry_rudy_dngl_earring", "name": "Beauchêne Ruby Earrings" },
                      { "id": "provision_jewelry_french_dmnd_ring", "name": "French Diamond Ring" }
                    ],
                    "game_id": "AWARD_ROLE_COLLECTOR_SET_SAINT_DENIS"
                  },
                  "source": { "name": "RDO Dailies", "url": "https://rdo-dailies.com" },
                  "attribution": "Data by MadamNazar.io — https://madamnazar.io",
                  "meta": {
                    "api_version": "v1",
                    "request_id": "01M5TA0…",
                    "requested_at": "2026-10-27T09:12:04.318Z",
                    "response_time_ms": 44,
                    "game_day": "2026-10-27",
                    "next_reset_at": "2026-10-28T06:00:00.000Z",
                    "valid_from": "2026-10-27T06:00:00.000Z",
                    "valid_until": "2026-10-28T06:00:00.000Z",
                    "data_updated_at": "2026-10-27T06:09:02.000Z",
                    "cache_ttl_seconds": 60,
                    "rate_limit": { "limit": 1000, "remaining": 997, "resets_at": "2026-10-28T00:00:00.000Z" },
                    "docs": "https://developer.madamnazar.io"
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "503": { "$ref": "#/components/responses/Unavailable" },
          "default": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/collector/jewelry": {
      "get": {
        "operationId": "getJewelry",
        "tags": ["collector"],
        "summary": "Get lost jewelry in static spots",
        "description": "Every lost jewelry item that can sit in a fixed spot, with `available_until` (null when not available) and `available` computed at request time.",
        "responses": {
          "200": {
            "description": "Get lost jewelry in static spots.",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" },
              "X-Request-Id": { "$ref": "#/components/headers/X-Request-Id" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/JewelryResponse" },
                "example": {
                  "date": "2026-10-27",
                  "updated_at": "2026-10-27T03:17:03.000Z",
                  "items": [
                    {
                      "id": "provision_jewelry_onyx_ring",
                      "name": "Aubrey Onyx Ring",
                      "available_until": "2026-10-30T06:00:00.000Z",
                      "available": true
                    },
                    {
                      "id": "provision_jewelry_emerald_ring",
                      "name": "Emerald Ring",
                      "available_until": null,
                      "available": false
                    }
                  ],
                  "source": { "name": "RDO Dailies", "url": "https://rdo-dailies.com" },
                  "attribution": "Data by MadamNazar.io — https://madamnazar.io",
                  "meta": {
                    "api_version": "v1",
                    "request_id": "01M5TA0…",
                    "requested_at": "2026-10-27T09:12:04.318Z",
                    "response_time_ms": 44,
                    "game_day": "2026-10-27",
                    "next_reset_at": "2026-10-28T06:00:00.000Z",
                    "valid_from": "2026-10-27T06:00:00.000Z",
                    "valid_until": "2026-10-28T06:00:00.000Z",
                    "data_updated_at": "2026-10-27T06:09:02.000Z",
                    "cache_ttl_seconds": 60,
                    "rate_limit": { "limit": 1000, "remaining": 997, "resets_at": "2026-10-28T00:00:00.000Z" },
                    "docs": "https://developer.madamnazar.io"
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "503": { "$ref": "#/components/responses/Unavailable" },
          "default": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/events": {
      "get": {
        "operationId": "getEvents",
        "tags": ["daily"],
        "summary": "Get the free roam event schedule",
        "description": "The daily free roam event schedule (UTC times), general and role events, and the next one of each.",
        "responses": {
          "200": {
            "description": "Get the free roam event schedule.",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" },
              "X-Request-Id": { "$ref": "#/components/headers/X-Request-Id" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/EventsResponse" },
                "example": {
                  "date": "2026-10-27",
                  "general": [
                    { "time_utc": "00:00", "id": "fme_cold_dead_hands", "name": "Cold Dead Hands", "variation": null },
                    {
                      "time_utc": "01:30",
                      "id": "fme_challenges",
                      "name": "Random Challenge",
                      "variation": { "id": "fme_challenge_wild_animals_kills", "name": "Wild Animal Kills" }
                    }
                  ],
                  "role": [
                    {
                      "time_utc": "00:22",
                      "id": "fme_role_protect_legendary_animal",
                      "name": "Protect Legendary Animal",
                      "variation": null
                    }
                  ],
                  "next": {
                    "general": {
                      "time_utc": "09:45",
                      "id": "fme_wildlife_photographer",
                      "name": "Wildlife Photographer",
                      "variation": null,
                      "starts_at": "2026-10-27T09:45:00.000Z"
                    },
                    "role": {
                      "time_utc": "10:52",
                      "id": "fme_role_manhunt",
                      "name": "Manhunt",
                      "variation": null,
                      "starts_at": "2026-10-27T10:52:00.000Z"
                    }
                  },
                  "source": { "name": "rdo.gg", "url": "https://rdo.gg" },
                  "attribution": "Data by MadamNazar.io — https://madamnazar.io",
                  "meta": {
                    "api_version": "v1",
                    "request_id": "01M5TA0…",
                    "requested_at": "2026-10-27T09:12:04.318Z",
                    "response_time_ms": 44,
                    "game_day": "2026-10-27",
                    "next_reset_at": "2026-10-28T06:00:00.000Z",
                    "valid_from": "2026-10-27T06:00:00.000Z",
                    "valid_until": "2026-10-28T06:00:00.000Z",
                    "data_updated_at": "2026-10-27T06:09:02.000Z",
                    "cache_ttl_seconds": 60,
                    "rate_limit": { "limit": 1000, "remaining": 997, "resets_at": "2026-10-28T00:00:00.000Z" },
                    "docs": "https://developer.madamnazar.io"
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "503": { "$ref": "#/components/responses/Unavailable" },
          "default": { "$ref": "#/components/responses/Error" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "Your key, `mn_live_…`. `Authorization: Bearer <key>` is also accepted."
      }
    },
    "headers": {
      "X-RateLimit-Limit": { "description": "Requests allowed per UTC day for this key.", "schema": { "type": "integer" } },
      "X-RateLimit-Remaining": { "description": "Requests left today.", "schema": { "type": "integer" } },
      "X-RateLimit-Reset": { "description": "Next 00:00 UTC, in Unix seconds.", "schema": { "type": "integer" } },
      "X-Request-Id": { "description": "Unique id of the request; include it when reporting a problem.", "schema": { "type": "string" } },
      "Retry-After": { "description": "Seconds to wait before retrying.", "schema": { "type": "integer" } }
    },
    "schemas": {
      "Location": {
        "type": "object",
        "required": ["name", "region"],
        "properties": {
          "name": { "type": "string", "description": "Spot name, for example \"point 8\"." },
          "region": { "type": "string", "description": "Region of the map, for example \"west elizabeth\"." },
          "cardinals": { "type": ["string", "null"], "description": "Where on the map, for example \"south east\"." },
          "image": { "type": ["string", "null"], "format": "uri", "description": "Map image of the spot." }
        }
      },
      "DayLocation": {
        "type": "object",
        "required": ["date", "location"],
        "properties": {
          "date": { "type": "string", "format": "date", "description": "Game day (starts at 06:00 UTC)." },
          "location": { "$ref": "#/components/schemas/Location" }
        }
      },
      "RateLimit": {
        "type": "object",
        "required": ["limit", "remaining", "resets_at"],
        "properties": {
          "limit": { "type": "integer" },
          "remaining": { "type": "integer" },
          "resets_at": { "type": "string", "format": "date-time" }
        }
      },
      "Meta": {
        "type": "object",
        "description": "Present on every response, errors included.",
        "required": ["api_version", "request_id", "requested_at", "response_time_ms", "game_day", "next_reset_at", "docs"],
        "properties": {
          "api_version": { "type": "string", "const": "v1" },
          "request_id": { "type": "string" },
          "requested_at": { "type": "string", "format": "date-time" },
          "response_time_ms": { "type": "integer" },
          "game_day": { "type": "string", "format": "date" },
          "next_reset_at": { "type": "string", "format": "date-time" },
          "valid_from": { "type": "string", "format": "date-time", "description": "Single-day responses only (location of one day and the daily data endpoints)." },
          "valid_until": { "type": "string", "format": "date-time", "description": "Single-day responses only (location of one day and the daily data endpoints)." },
          "location_updated_at": { "type": ["string", "null"], "format": "date-time", "description": "Single-day responses only (location of one day and the daily data endpoints)." },
          "data_updated_at": { "type": ["string", "null"], "format": "date-time", "description": "Daily data endpoints only: when the data was fetched." },
          "cache_ttl_seconds": { "type": "integer", "description": "Single-day responses only (location of one day and the daily data endpoints)." },
          "rate_limit": { "$ref": "#/components/schemas/RateLimit" },
          "docs": { "type": "string", "format": "uri" }
        }
      },
      "LocationResponse": {
        "allOf": [
          { "$ref": "#/components/schemas/DayLocation" },
          {
            "type": "object",
            "required": ["attribution", "meta"],
            "properties": {
              "attribution": { "type": "string", "description": "Text to display with a link to madamnazar.io." },
              "meta": { "$ref": "#/components/schemas/Meta" }
            }
          }
        ]
      },
      "HistoryResponse": {
        "type": "object",
        "required": ["data", "count", "attribution", "meta"],
        "properties": {
          "data": { "type": "array", "items": { "$ref": "#/components/schemas/DayLocation" } },
          "count": { "type": "integer" },
          "attribution": { "type": "string" },
          "meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "Source": {
        "type": "object",
        "description": "Where this data comes from. Credit it next to `attribution`.",
        "required": ["name", "url"],
        "properties": { "name": { "type": "string" }, "url": { "type": "string", "format": "uri" } }
      },
      "Challenge": {
        "type": "object",
        "required": ["id", "description"],
        "properties": {
          "id": { "type": "string", "description": "Rockstar challenge id." },
          "goal": { "type": ["integer", "number", "null"], "description": "Target to reach." },
          "description": { "type": "string", "description": "English text, without the progress counter." }
        }
      },
      "ChallengeSet": {
        "type": "object",
        "required": ["role", "challenges"],
        "properties": {
          "role": {
            "type": "string",
            "description": "`general`, or a role: bounty_hunter, trader, collector, moonshiner, naturalist."
          },
          "starts_at": { "type": ["string", "null"], "format": "date-time" },
          "ends_at": { "type": ["string", "null"], "format": "date-time" },
          "challenges": { "type": "array", "items": { "$ref": "#/components/schemas/Challenge" } }
        }
      },
      "ChallengesResponse": {
        "type": "object",
        "required": ["date", "general", "roles", "source", "attribution", "meta"],
        "properties": {
          "general": { "$ref": "#/components/schemas/ChallengeSet" },
          "roles": {
            "type": "object",
            "properties": {
              "easy": { "type": "array", "items": { "$ref": "#/components/schemas/ChallengeSet" } },
              "medium": { "type": "array", "items": { "$ref": "#/components/schemas/ChallengeSet" } },
              "hard": { "type": "array", "items": { "$ref": "#/components/schemas/ChallengeSet" } }
            }
          },
          "source": { "$ref": "#/components/schemas/Source" },
          "attribution": { "type": "string", "description": "Text to display with a link to madamnazar.io." },
          "meta": { "$ref": "#/components/schemas/Meta" },
          "date": { "type": "string", "format": "date", "description": "Game day (starts at 06:00 UTC)." }
        }
      },
      "WeeklySetResponse": {
        "type": "object",
        "required": ["date", "set", "source", "attribution", "meta"],
        "properties": {
          "set": {
            "type": "object",
            "required": ["id", "items"],
            "properties": {
              "id": { "type": "string", "description": "For example set_saint_denis." },
              "name": { "type": ["string", "null"] },
              "value": { "type": ["integer", "null"], "description": "What Madam Nazar pays for the set, in dollars." },
              "items": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": ["id"],
                  "properties": { "id": { "type": "string" }, "name": { "type": ["string", "null"] } }
                }
              },
              "game_id": { "type": "string", "description": "Rockstar award id." }
            }
          },
          "source": { "$ref": "#/components/schemas/Source" },
          "attribution": { "type": "string", "description": "Text to display with a link to madamnazar.io." },
          "meta": { "$ref": "#/components/schemas/Meta" },
          "date": { "type": "string", "format": "date", "description": "Game day (starts at 06:00 UTC)." }
        }
      },
      "JewelryResponse": {
        "type": "object",
        "required": ["date", "items", "source", "attribution", "meta"],
        "properties": {
          "updated_at": { "type": ["string", "null"], "format": "date-time" },
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["id", "available_until", "available"],
              "properties": {
                "id": { "type": "string" },
                "name": { "type": ["string", "null"] },
                "available_until": {
                  "type": ["string", "null"],
                  "format": "date-time",
                  "description": "End of availability in its static spot; null when not available."
                },
                "available": { "type": "boolean" }
              }
            }
          },
          "source": { "$ref": "#/components/schemas/Source" },
          "attribution": { "type": "string", "description": "Text to display with a link to madamnazar.io." },
          "meta": { "$ref": "#/components/schemas/Meta" },
          "date": { "type": "string", "format": "date", "description": "Game day (starts at 06:00 UTC)." }
        }
      },
      "FreeRoamEvent": {
        "type": "object",
        "required": ["time_utc", "id", "name"],
        "properties": {
          "time_utc": { "type": "string", "description": "HH:MM, UTC, every day." },
          "id": { "type": "string" },
          "name": { "type": "string" },
          "variation": {
            "type": ["object", "null"],
            "properties": { "id": { "type": "string" }, "name": { "type": "string" } }
          },
          "starts_at": { "type": "string", "format": "date-time", "description": "In `next` only." }
        }
      },
      "EventsResponse": {
        "type": "object",
        "required": ["date", "general", "role", "next", "source", "attribution", "meta"],
        "properties": {
          "general": { "type": "array", "items": { "$ref": "#/components/schemas/FreeRoamEvent" } },
          "role": { "type": "array", "items": { "$ref": "#/components/schemas/FreeRoamEvent" } },
          "next": {
            "type": "object",
            "properties": {
              "general": { "$ref": "#/components/schemas/FreeRoamEvent" },
              "role": { "$ref": "#/components/schemas/FreeRoamEvent" }
            }
          },
          "source": { "$ref": "#/components/schemas/Source" },
          "attribution": { "type": "string", "description": "Text to display with a link to madamnazar.io." },
          "meta": { "$ref": "#/components/schemas/Meta" },
          "date": { "type": "string", "format": "date", "description": "Game day (starts at 06:00 UTC)." }
        }
      },
      "Error": {
        "type": "object",
        "required": ["error", "message", "meta"],
        "properties": {
          "error": {
            "type": "string",
            "description": "Stable error code.",
            "enum": [
              "invalid_date",
              "missing_api_key",
              "invalid_api_key",
              "subscription_inactive",
              "not_found",
              "no_location_for_date",
              "method_not_allowed",
              "rate_limited",
              "server_misconfigured",
              "server_error",
              "location_unavailable",
              "location_not_confirmed_yet",
              "not_published_yet",
              "data_unavailable",
              "plan_upgrade_required"
            ]
          },
          "message": { "type": "string", "description": "What went wrong and how to fix it." },
          "docs": { "type": "string", "format": "uri" },
          "openapi": { "type": "string", "format": "uri" },
          "meta": { "$ref": "#/components/schemas/Meta" }
        },
        "example": {
          "error": "missing_api_key",
          "message": "Send your key in the x-api-key header. Get one at https://developer.madamnazar.io",
          "meta": {
            "api_version": "v1",
            "request_id": "01M5T8GK73WBWWM2C9JDRT08V1",
            "requested_at": "2026-10-26T09:12:04.318Z",
            "response_time_ms": 3,
            "game_day": "2026-10-26",
            "next_reset_at": "2026-10-27T06:00:00.000Z",
            "docs": "https://developer.madamnazar.io"
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "`invalid_date`: the date parameter is not YYYY-MM-DD.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Unauthorized": {
        "description": "`missing_api_key` or `invalid_api_key`.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Forbidden": {
        "description": "`subscription_inactive`: the key's subscription was canceled or isn't paid. `plan_upgrade_required`: free keys only reach `/v1/location/today`.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "NotFound": {
        "description": "`no_location_for_date`, or `not_found` for an unknown endpoint.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "RateLimited": {
        "description": "`rate_limited`: daily quota reached; it resets at 00:00 UTC.",
        "headers": { "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" } },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Unavailable": {
        "description": "`location_not_confirmed_yet` or `not_published_yet` just after the reset, `location_unavailable` or `data_unavailable` when a store or upstream is down. Retry after `Retry-After` seconds.",
        "headers": { "Retry-After": { "$ref": "#/components/headers/Retry-After" } },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Error": {
        "description": "Any other error (`method_not_allowed`, `server_error`…).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    }
  }
}
