{
  "openapi": "3.1.0",
  "info": {
    "title": "China Footnotes",
    "version": "0.1.0",
    "description": "Read-only China city guide (Shanghai first; more cities as each is reviewed). Unknown is not false. Interpret Field.status before Field.value. Source reading does not establish booking success, current availability or foreign-document support."
  },
  "servers": [
    {
      "url": "/",
      "description": "Same origin as this specification"
    }
  ],
  "paths": {
    "/api/places": {
      "get": {
        "operationId": "searchPlaces",
        "summary": "Search canonical places with evidence and monitoring context",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "English/Chinese names, aliases or description; quote variants normalized"
          },
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Category, case insensitive"
          },
          {
            "name": "district",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "District membership; quote variants normalized and multi-district places belong to either district"
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Place type: open_district, water_town, museum, observation_deck, ticketed_attraction, classical_garden, park, temple.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tag",
            "in": "query",
            "required": false,
            "description": "Feature tag, e.g. free, booking-required, night-view, open-air.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "city",
            "in": "query",
            "required": false,
            "description": "City id, e.g. shanghai. Only published cities; see /api/cities. An unknown city is a 404.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Search canonical places with evidence and monitoring context",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlaceList"
                }
              }
            }
          },
          "400": {
            "description": "Invalid URL encoding"
          },
          "404": {
            "description": "City not found (not published)"
          },
          "500": {
            "description": "Local data/service unavailable"
          }
        }
      }
    },
    "/api/places/{id}": {
      "get": {
        "operationId": "getPlace",
        "summary": "Read one place and its evidence; retain status and pending-review warnings",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Read one place and its evidence; retain status and pending-review warnings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlaceDetail"
                }
              }
            }
          },
          "400": {
            "description": "Invalid URL encoding"
          },
          "404": {
            "description": "Place ID not found"
          },
          "500": {
            "description": "Local data/service unavailable"
          }
        }
      }
    },
    "/api/updates": {
      "get": {
        "operationId": "getUpdates",
        "summary": "Recorded updates active on today’s Asia/Shanghai date, including source references",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Recorded updates active on today’s Asia/Shanghai date, including source references",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateList"
                }
              }
            }
          },
          "400": {
            "description": "Invalid URL encoding"
          },
          "500": {
            "description": "Local data/service unavailable"
          }
        }
      }
    },
    "/api/status": {
      "get": {
        "operationId": "getDataStatus",
        "summary": "Dataset and local source-monitor status; not live availability",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Dataset and local source-monitor status; not live availability",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Status"
                }
              }
            }
          },
          "400": {
            "description": "Invalid URL encoding"
          },
          "500": {
            "description": "Local data/service unavailable"
          }
        }
      }
    },
    "/api/pretrip": {
      "get": {
        "summary": "Before-you-go topics in three stages (plan: when-to-go, visa; setup: payments, internet, apps, book-ahead; ground: getting-around, safety). Each has stage, overview, keyFact and items; when-to-go adds calendar and climate (one city’s monthly averages, named by climateCity and climateCityId; every city’s own table is City.weather in /api/cities), visa adds routes with country lists, apps adds apps, book-ahead adds bookFirst and seasonal (each entry with city and cityName), placeCount and cities, all computed from the places of the published cities; its keyFact and overview carry the same count (\"N of our M Shanghai places\", or \"N of our M places across K cities\"). Expired items, tips and past calendar entries are removed for today in Shanghai",
        "responses": {
          "200": {
            "description": "Topics in reading order"
          }
        }
      }
    },
    "/api/pretrip/{id}": {
      "get": {
        "summary": "One before-you-go topic",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "when-to-go",
                "visa",
                "payments",
                "internet",
                "apps",
                "book-ahead",
                "getting-around",
                "safety"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Topic"
          },
          "301": {
            "description": "Old topic id (entry-documents, maps-transport, arrival-day, booking-id, language, holidays-crowds); Location gives the new topic"
          },
          "404": {
            "description": "Unknown topic"
          }
        }
      }
    },
    "/api/cities": {
      "get": {
        "operationId": "listCities",
        "summary": "Published cities with place counts, areas, accent colours and city facts",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Published cities",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CityList"
                }
              }
            }
          },
          "400": {
            "description": "Invalid URL encoding"
          },
          "500": {
            "description": "Local data/service unavailable"
          }
        }
      }
    },
    "/api/cities/{id}": {
      "get": {
        "operationId": "getCity",
        "summary": "One published city and the list of its places",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "City id, e.g. shanghai"
          }
        ],
        "responses": {
          "200": {
            "description": "One city",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CityDetail"
                }
              }
            }
          },
          "400": {
            "description": "Invalid URL encoding"
          },
          "404": {
            "description": "City not found (not published)"
          },
          "500": {
            "description": "Local data/service unavailable"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Field": {
        "type": "object",
        "properties": {
          "value": {
            "type": [
              "string",
              "null"
            ],
            "description": "May contain explanation even when status is unknown or conflicting. Never infer confirmation from nonempty value."
          },
          "status": {
            "type": "string",
            "enum": [
              "source_checked",
              "unknown",
              "conflicting",
              "not_applicable"
            ],
            "description": "source_checked = a published source was read; not a booking or on-site test. not_applicable means no single policy covers this scoped place; it does not mean free or no booking."
          },
          "sourceIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "value",
          "status",
          "sourceIds"
        ]
      },
      "Source": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "title": {
            "type": "string"
          },
          "publisher": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "official",
              "government"
            ]
          },
          "accessedAt": {
            "type": "string",
            "format": "date"
          },
          "publishedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          }
        },
        "required": [
          "id",
          "url",
          "title",
          "publisher",
          "kind",
          "accessedAt"
        ]
      },
      "Tip": {
        "type": "object",
        "properties": {
          "text": {
            "type": "string"
          },
          "topic": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "const": "traveler_report"
          },
          "sourceUrl": {
            "type": "string",
            "format": "uri"
          },
          "sourceTitle": {
            "type": "string"
          },
          "author": {
            "type": "string"
          },
          "observedDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "observedDateRaw": {
            "type": [
              "string",
              "null"
            ],
            "description": "Original visible date label. It can lack a year or include locale text; never manufacture a year."
          },
          "visitDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "accessedAt": {
            "type": "string",
            "format": "date"
          },
          "caveat": {
            "type": "string"
          },
          "commercial": {
            "type": "boolean"
          }
        },
        "required": [
          "text",
          "kind",
          "sourceUrl",
          "accessedAt"
        ]
      },
      "Photo": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string"
          },
          "sourceUrl": {
            "type": "string"
          },
          "author": {
            "type": "string"
          },
          "license": {
            "type": "string"
          },
          "licenseUrl": {
            "type": "string"
          },
          "caption": {
            "type": "string"
          },
          "alt": {
            "type": "string"
          },
          "accessedAt": {
            "type": "string",
            "format": "date"
          }
        },
        "required": [
          "url",
          "sourceUrl",
          "author",
          "license"
        ]
      },
      "Update": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "publishedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "reviewedAt": {
            "type": "string",
            "format": "date"
          },
          "startsAt": {
            "type": "string",
            "format": "date"
          },
          "expiresAt": {
            "type": "string",
            "format": "date"
          },
          "sourceIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "placeId": {
            "type": "string"
          },
          "sources": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Source"
            }
          }
        },
        "required": [
          "id",
          "text",
          "publishedAt",
          "reviewedAt",
          "startsAt",
          "expiresAt",
          "sourceIds"
        ]
      },
      "SourceMonitoring": {
        "type": "object",
        "properties": {
          "sourceId": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "not_checked",
              "baseline",
              "unchanged",
              "changed",
              "failed"
            ]
          },
          "lastSuccessAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "lastAttemptAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "pendingReviewIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "sourceId",
          "status",
          "pendingReviewIds"
        ]
      },
      "Place": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "localName": {
            "type": "string"
          },
          "district": {
            "type": [
              "string",
              "null"
            ],
            "description": "District name; null where not collected yet."
          },
          "summary": {
            "type": "string"
          },
          "number": {
            "type": "integer",
            "minimum": 1,
            "description": "1–50 are Shanghai; other cities are numbered from 51."
          },
          "category": {
            "type": "string",
            "enum": [
              "Landmarks",
              "Museums",
              "Neighborhoods",
              "Gardens",
              "Temples",
              "Water towns"
            ]
          },
          "aliases": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "highlights": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "visitDuration": {
            "type": [
              "string",
              "null"
            ],
            "description": "Editorial planning suggestion, not a venue rule."
          },
          "researchedAt": {
            "type": "string",
            "format": "date"
          },
          "sources": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Source"
            }
          },
          "tips": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Tip"
            }
          },
          "photos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Photo"
            }
          },
          "updates": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Update"
            }
          },
          "dataQuality": {
            "type": "object",
            "properties": {
              "knownFields": {
                "type": "integer"
              },
              "totalFields": {
                "type": "integer"
              },
              "method": {
                "type": "string"
              },
              "reviewDue": {
                "type": "string",
                "format": "date"
              }
            },
            "required": []
          },
          "sourceMonitoring": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SourceMonitoring"
            }
          },
          "hasPendingSourceReview": {
            "type": "boolean"
          },
          "isReviewOverdue": {
            "type": "boolean"
          },
          "address": {
            "$ref": "#/components/schemas/Field"
          },
          "transport": {
            "$ref": "#/components/schemas/Field"
          },
          "hours": {
            "$ref": "#/components/schemas/Field"
          },
          "admission": {
            "$ref": "#/components/schemas/Field"
          },
          "booking": {
            "$ref": "#/components/schemas/Field"
          },
          "foreignVisitors": {
            "$ref": "#/components/schemas/Field"
          },
          "accessibility": {
            "$ref": "#/components/schemas/Field"
          },
          "lastAdmission": {
            "$ref": "#/components/schemas/Field"
          },
          "city": {
            "type": "string",
            "description": "City id, e.g. shanghai"
          },
          "area": {
            "type": [
              "string",
              "null"
            ],
            "description": "Area id within the city (see City.areas)"
          },
          "group": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "town",
              "trips",
              null
            ],
            "description": "For cities split into In town and Day trips (see City.groups)"
          },
          "distanceKm": {
            "type": [
              "number",
              "null"
            ],
            "description": "Day trips only: rough distance from the centre; null when not checked"
          },
          "shortName": {
            "type": "string",
            "description": "Short label for maps and lists"
          }
        },
        "required": [
          "id",
          "name",
          "localName",
          "category",
          "summary",
          "highlights",
          "researchedAt",
          "sources",
          "tips",
          "photos",
          "updates"
        ]
      },
      "PlaceList": {
        "type": "object",
        "properties": {
          "schemaVersion": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "notice": {
            "type": "string"
          },
          "total": {
            "type": "integer"
          },
          "places": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Place"
            }
          }
        },
        "required": [
          "total",
          "places"
        ]
      },
      "PlaceDetail": {
        "type": "object",
        "properties": {
          "schemaVersion": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "notice": {
            "type": "string"
          },
          "place": {
            "$ref": "#/components/schemas/Place"
          },
          "reviewDue": {
            "type": "string",
            "format": "date"
          },
          "isReviewOverdue": {
            "type": "boolean"
          },
          "sourceMonitoring": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SourceMonitoring"
            }
          },
          "hasPendingSourceReview": {
            "type": "boolean"
          }
        },
        "required": [
          "place",
          "sourceMonitoring",
          "hasPendingSourceReview"
        ]
      },
      "UpdateList": {
        "type": "object",
        "properties": {
          "schemaVersion": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "notice": {
            "type": "string"
          },
          "updates": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Update"
            }
          }
        },
        "required": [
          "updates"
        ]
      },
      "Monitoring": {
        "type": "object",
        "properties": {
          "checked": {
            "type": "integer"
          },
          "totalSources": {
            "type": "integer"
          },
          "baseline": {
            "type": "integer"
          },
          "unchanged": {
            "type": "integer"
          },
          "changed": {
            "type": "integer"
          },
          "failed": {
            "type": "integer"
          },
          "pendingReviews": {
            "type": "integer"
          },
          "startedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "completedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "nextScheduledAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "factsAutomaticallyPublished": {
            "const": false
          },
          "results": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "url": {
                  "type": "string"
                },
                "status": {
                  "type": "string"
                },
                "error": {
                  "type": "string"
                }
              },
              "required": []
            }
          }
        },
        "required": []
      },
      "Status": {
        "type": "object",
        "properties": {
          "schemaVersion": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "notice": {
            "type": "string"
          },
          "placeCount": {
            "type": "integer"
          },
          "tipCount": {
            "type": "integer"
          },
          "reviewOverdue": {
            "type": "integer"
          },
          "pendingReviews": {
            "type": "integer"
          },
          "monitoringStale": {
            "type": "boolean",
            "description": "No successful scan summary in the past 36 hours. Does not prove a process is alive."
          },
          "monitoring": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Monitoring"
              },
              {
                "type": "null"
              }
            ]
          },
          "cities": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "placeCount": {
                  "type": "integer"
                },
                "preview": {
                  "type": "boolean"
                }
              }
            },
            "description": "Place count per published city"
          }
        },
        "required": [
          "placeCount",
          "monitoringStale",
          "pendingReviews"
        ]
      },
      "City": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "zh": {
            "type": "string"
          },
          "live": {
            "type": "boolean"
          },
          "preview": {
            "type": "boolean",
            "description": "Only in local preview builds of unreviewed data"
          },
          "placeCount": {
            "type": "integer"
          },
          "accent": {
            "type": "string",
            "description": "#rrggbb"
          },
          "accentSoft": {
            "type": "string",
            "description": "#rrggbb"
          },
          "tagline": {
            "type": [
              "string",
              "null"
            ],
            "description": "null until written"
          },
          "stats": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "value": {
                  "type": [
                    "number",
                    "string",
                    "null"
                  ]
                },
                "unit": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "source": {},
                "asOf": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            },
            "description": "null until collected"
          },
          "weather": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "object",
              "properties": {
                "month": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 12
                },
                "highC": {
                  "type": "number"
                },
                "lowC": {
                  "type": "number"
                },
                "icon": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "sun",
                    "cloud",
                    "rain",
                    "snow",
                    "haze",
                    null
                  ]
                },
                "rainDays": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "source": {}
              }
            },
            "description": "12 months, or null"
          },
          "areas": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "zh": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "line": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "numbers": {
                  "type": "array",
                  "items": {
                    "type": "integer"
                  }
                },
                "focus": {
                  "type": "array",
                  "items": {
                    "type": "integer"
                  }
                }
              }
            }
          },
          "groups": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "town": {
                "type": "array",
                "items": {
                  "type": "integer"
                }
              },
              "trips": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "number": {
                      "type": "integer"
                    },
                    "distanceKm": {
                      "type": [
                        "number",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "bookFirst": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Place ids that need booking ahead, from the place booking data"
          },
          "arrivals": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "zh": {
                  "type": "string"
                },
                "kind": {
                  "type": "string",
                  "enum": [
                    "airport",
                    "station"
                  ]
                },
                "how": {
                  "type": "string",
                  "description": "One-line summary"
                },
                "routes": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "mode": {
                        "type": "string",
                        "enum": [
                          "metro",
                          "maglev",
                          "rail",
                          "bus",
                          "taxi"
                        ]
                      },
                      "text": {
                        "type": "string"
                      },
                      "fare": {
                        "type": [
                          "object",
                          "null"
                        ],
                        "description": "Yuan; min null = up to max",
                        "properties": {
                          "min": {
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "max": {
                            "type": "number"
                          },
                          "approx": {
                            "type": "boolean"
                          }
                        }
                      },
                      "durationMin": {
                        "type": [
                          "integer",
                          "null"
                        ]
                      },
                      "lastService": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "HH:MM"
                      },
                      "serviceHours": {
                        "type": "string",
                        "description": "HH:MM–HH:MM"
                      }
                    }
                  }
                },
                "caveats": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "text": {
                        "type": "string"
                      },
                      "sourceIds": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      }
                    }
                  }
                },
                "tips": {
                  "type": "array",
                  "description": "Traveller reports, each with its evidence",
                  "items": {
                    "type": "object",
                    "properties": {
                      "title": {
                        "type": "string"
                      },
                      "body": {
                        "type": "string"
                      },
                      "caveat": {
                        "type": "string"
                      },
                      "evidence": {
                        "type": "array",
                        "items": {
                          "type": "object"
                        }
                      }
                    }
                  }
                },
                "gaps": {
                  "type": "array",
                  "description": "Not found yet, with the search record",
                  "items": {
                    "type": "object",
                    "properties": {
                      "reason": {
                        "type": "string"
                      },
                      "searched": {
                        "type": "array",
                        "items": {
                          "type": "object"
                        }
                      },
                      "nextStep": {
                        "type": "string"
                      }
                    }
                  }
                },
                "checkedAt": {
                  "type": "string",
                  "format": "date"
                },
                "sourceIds": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "Ids in City.sources"
                }
              }
            }
          },
          "nextStops": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "object",
              "properties": {
                "city": {
                  "type": "string"
                },
                "trainTime": {},
                "source": {}
              }
            },
            "description": "Other cities reachable by train, listed only when that city is published in this build"
          },
          "photo": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "src": {
                "type": "string"
              },
              "credit": {
                "type": "string"
              },
              "href": {
                "type": "string"
              },
              "rights": {
                "type": "string"
              }
            }
          },
          "skyline": {
            "type": [
              "string",
              "null"
            ]
          },
          "scene": {
            "type": [
              "object",
              "null"
            ]
          },
          "metroColors": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "minItems": 2,
              "maxItems": 2
            },
            "description": "Line → [background, ink]"
          },
          "ratings": {
            "type": [
              "object",
              "null"
            ],
            "description": "Editorial ratings comparing the cities on this site; subjective, not an official ranking. plan = how early to book (shown as calendars; more = book earlier), not better or worse.",
            "properties": {
              "history": {
                "type": "object",
                "properties": {
                  "stars": {
                    "type": "number",
                    "minimum": 1,
                    "maximum": 5,
                    "multipleOf": 0.5
                  },
                  "display": {
                    "type": "string",
                    "enum": [
                      "stars"
                    ]
                  },
                  "overview": {
                    "type": "string",
                    "maxLength": 220,
                    "description": "Shown when the reader opens the rating"
                  },
                  "reason": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Why this rating; may repeat the overview"
                  }
                },
                "required": [
                  "stars",
                  "display",
                  "overview",
                  "reason"
                ]
              },
              "food": {
                "type": "object",
                "properties": {
                  "stars": {
                    "type": "number",
                    "minimum": 1,
                    "maximum": 5,
                    "multipleOf": 0.5
                  },
                  "display": {
                    "type": "string",
                    "enum": [
                      "stars"
                    ]
                  },
                  "overview": {
                    "type": "string",
                    "maxLength": 220,
                    "description": "Shown when the reader opens the rating"
                  },
                  "reason": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Why this rating; may repeat the overview"
                  }
                },
                "required": [
                  "stars",
                  "display",
                  "overview",
                  "reason"
                ]
              },
              "cityscape": {
                "type": "object",
                "properties": {
                  "stars": {
                    "type": "number",
                    "minimum": 1,
                    "maximum": 5,
                    "multipleOf": 0.5
                  },
                  "display": {
                    "type": "string",
                    "enum": [
                      "stars"
                    ]
                  },
                  "overview": {
                    "type": "string",
                    "maxLength": 220,
                    "description": "Shown when the reader opens the rating"
                  },
                  "reason": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Why this rating; may repeat the overview"
                  }
                },
                "required": [
                  "stars",
                  "display",
                  "overview",
                  "reason"
                ]
              },
              "nature": {
                "type": "object",
                "properties": {
                  "stars": {
                    "type": "number",
                    "minimum": 1,
                    "maximum": 5,
                    "multipleOf": 0.5
                  },
                  "display": {
                    "type": "string",
                    "enum": [
                      "stars"
                    ]
                  },
                  "overview": {
                    "type": "string",
                    "maxLength": 220,
                    "description": "Shown when the reader opens the rating"
                  },
                  "reason": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Why this rating; may repeat the overview"
                  }
                },
                "required": [
                  "stars",
                  "display",
                  "overview",
                  "reason"
                ]
              },
              "shopping": {
                "type": "object",
                "properties": {
                  "stars": {
                    "type": "number",
                    "minimum": 1,
                    "maximum": 5,
                    "multipleOf": 0.5
                  },
                  "display": {
                    "type": "string",
                    "enum": [
                      "stars"
                    ]
                  },
                  "overview": {
                    "type": "string",
                    "maxLength": 220,
                    "description": "Shown when the reader opens the rating"
                  },
                  "reason": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Why this rating; may repeat the overview"
                  }
                },
                "required": [
                  "stars",
                  "display",
                  "overview",
                  "reason"
                ]
              },
              "plan": {
                "type": "object",
                "properties": {
                  "stars": {
                    "type": "number",
                    "minimum": 1,
                    "maximum": 5,
                    "multipleOf": 0.5
                  },
                  "display": {
                    "type": "string",
                    "enum": [
                      "calendar"
                    ]
                  },
                  "overview": {
                    "type": "string",
                    "maxLength": 220,
                    "description": "Shown when the reader opens the rating"
                  },
                  "reason": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Why this rating; may repeat the overview"
                  }
                },
                "required": [
                  "stars",
                  "display",
                  "overview",
                  "reason"
                ]
              }
            },
            "required": [
              "history",
              "food",
              "cityscape",
              "nature",
              "shopping",
              "plan"
            ]
          },
          "images": {
            "type": [
              "object",
              "null"
            ],
            "description": "City illustrations: hero (city page header), card (city card, cropped from the hero at objectPosition) and bg (home background)",
            "properties": {
              "hero": {
                "type": "object",
                "properties": {
                  "sizes": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "src": {
                          "type": "string"
                        },
                        "width": {
                          "type": "integer"
                        },
                        "height": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "src",
                        "width",
                        "height"
                      ]
                    },
                    "description": "WebP sizes, small to large (use as srcset)"
                  },
                  "objectPosition": {
                    "type": "string"
                  }
                }
              },
              "card": {
                "type": "object",
                "properties": {
                  "from": {
                    "type": "string",
                    "enum": [
                      "hero"
                    ]
                  },
                  "objectPosition": {
                    "type": "string"
                  }
                }
              },
              "bg": {
                "type": "object",
                "properties": {
                  "sizes": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "src": {
                          "type": "string"
                        },
                        "width": {
                          "type": "integer"
                        },
                        "height": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "src",
                        "width",
                        "height"
                      ]
                    },
                    "description": "WebP sizes, small to large (use as srcset)"
                  },
                  "objectPosition": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "transit": {
            "type": [
              "object",
              "null"
            ],
            "description": "The city metro: how to pay and ride",
            "properties": {
              "name": {
                "type": "string"
              },
              "zh": {
                "type": "string"
              },
              "summary": {
                "type": "string"
              },
              "steps": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "fares": {
                "type": "object",
                "description": "Yuan amounts and a note"
              },
              "caveats": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "text": {
                      "type": "string"
                    },
                    "sourceIds": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              },
              "tips": {
                "type": "array",
                "description": "Traveller reports, each with its evidence",
                "items": {
                  "type": "object",
                  "properties": {
                    "title": {
                      "type": "string"
                    },
                    "body": {
                      "type": "string"
                    },
                    "caveat": {
                      "type": "string"
                    },
                    "evidence": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              },
              "checkedAt": {
                "type": "string",
                "format": "date"
              },
              "sourceIds": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "sources": {
            "type": "array",
            "description": "Sources cited by arrivals, transit and notes (sourceIds)",
            "items": {
              "$ref": "#/components/schemas/Source"
            }
          }
        },
        "required": [
          "id",
          "name",
          "zh",
          "live",
          "placeCount",
          "accent",
          "accentSoft"
        ]
      },
      "CityList": {
        "type": "object",
        "properties": {
          "schemaVersion": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "notice": {
            "type": "string"
          },
          "total": {
            "type": "integer"
          },
          "cities": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/City"
            }
          }
        },
        "required": [
          "total",
          "cities"
        ]
      },
      "CityDetail": {
        "type": "object",
        "properties": {
          "schemaVersion": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "notice": {
            "type": "string"
          },
          "city": {
            "$ref": "#/components/schemas/City"
          },
          "places": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "number": {
                  "type": "integer"
                },
                "name": {
                  "type": "string"
                },
                "nameZh": {
                  "type": "string"
                },
                "shortName": {
                  "type": "string"
                },
                "area": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "group": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "type": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "url": {
                  "type": "string"
                },
                "api": {
                  "type": "string"
                }
              }
            }
          }
        },
        "required": [
          "city",
          "places"
        ]
      }
    }
  }
}