openapi: 3.0.3
info:
  title: geoAPI
  description: |
    Mehrsprachiges Reverse-Geocoding und Ortssuche.
    5,7 Mio. Orte, 10,6 Mio. Namen, JSON-Antworten, kostenfrei für Free-anonym-Tier.

    Authentifizierung optional über `?key=…`. Ohne Key: 200 Anfragen / Tag / IP.
    Mit Key: tier-abhängige monatliche Limits (siehe `/preise.php`).
  version: 1.0.0
  contact:
    email: ai@rd5.org
    url: https://geoapi.world
  license:
    name: GeoNames Daten unter CC BY 4.0
    url: https://creativecommons.org/licenses/by/4.0/

servers:
  - url: https://geoapi.world/api/v1
    description: Produktion (versioniert, empfohlen)
  - url: https://geoapi.world/api
    description: Legacy (nicht versioniert, künftig deprecated)

paths:

  /search.php:
    get:
      summary: Ortssuche per Substring
      description: Sucht Orte deren Name (oder Alternativnamen) mit dem Query-String beginnen oder ihn enthalten. Sortierung nach Einwohnerzahl absteigend.
      parameters:
        - name: q
          in: query
          required: true
          description: Suchbegriff. Mindestens 2 Zeichen.
          schema: { type: string, minLength: 2 }
        - name: limit
          in: query
          description: Maximale Trefferanzahl (1–30).
          schema: { type: integer, minimum: 1, maximum: 30, default: 10 }
        - name: country
          in: query
          description: Komma-separierte Liste von ISO-2-Ländercodes — Suche auf diese Länder beschränken.
          schema: { type: string, example: "DE,AT,CH" }
        - name: extended
          in: query
          description: |
            Granular wählbare Zusatz-Blöcke (kommagetrennt). Erlaubte Werte:
            `country`, `admin`, `timezone`, `flag`, `localized`.
            Aliase: `1` = `country,admin,timezone` (Legacy), `all` = alle Blöcke.
            Pro Block fällt 1 indexgestützte Batch-Query an, unabhängig von der Trefferzahl.
          schema:
            type: string
            example: "country,admin,flag"
        - name: flag
          in: query
          description: "Wenn `1`: aktiviert den `flag`-Block (Convenience für `extended=…,flag`)."
          schema: { type: string, enum: ["0","1"], default: "0" }
        - name: lang
          in: query
          description: |
            ISO-Sprachcode für lokalisierte Namen (z. B. `de`, `fr`, `es`, `zh`).
            Aktiviert automatisch den `localized`-Block. Überschreibt `name`,
            `country.name`, `admin1.name` mit der Sprach-Variante aus
            `geo_alternate_names` (Fallback: englischer Default-Name).
          schema: { type: string, example: "de" }
        - name: key
          in: query
          description: Optionaler API-Key.
          schema: { type: string }
      responses:
        '200':
          description: Trefferliste
          content:
            application/json:
              schema:
                type: object
                properties:
                  query:    { type: string }
                  count:    { type: integer }
                  ms:       { type: number }
                  extended: { type: array, items: { type: string } }
                  lang:     { type: string, nullable: true }
                  results:
                    type: array
                    items: { $ref: '#/components/schemas/Place' }
        '500':
          $ref: '#/components/responses/Error'

  /lookup.php:
    get:
      summary: Reverse-Geocoding per Koordinaten
      description: Liefert den nächsten Ort zu Lat/Lon. Modus `city` bevorzugt bewohnte Städte (für Astrologie/Wetter), `exact` den nächsten Punkt überhaupt.
      parameters:
        - name: lat
          in: query
          required: true
          schema: { type: number, format: double, minimum: -90, maximum: 90 }
        - name: lon
          in: query
          required: true
          schema: { type: number, format: double, minimum: -180, maximum: 180 }
        - name: mode
          in: query
          schema:
            type: string
            enum: [city, exact]
            default: city
        - name: extended
          in: query
          description: |
            Granular wählbare Zusatz-Blöcke (kommagetrennt). Erlaubte Werte:
            `country`, `admin`, `timezone`, `flag`, `localized`.
            Aliase: `1` = `country,admin,timezone` (Legacy), `all` = alle Blöcke.
          schema:
            type: string
            example: "country,flag"
        - name: flag
          in: query
          description: "Wenn `1`: aktiviert den `flag`-Block (Convenience)."
          schema: { type: string, enum: ["0","1"], default: "0" }
        - name: lang
          in: query
          description: ISO-Sprachcode für lokalisierte Namen (z. B. `de`, `fr`). Aktiviert `localized`.
          schema: { type: string, example: "de" }
        - name: key
          in: query
          schema: { type: string }
      responses:
        '200':
          description: Treffer
          content:
            application/json:
              schema:
                type: object
                properties:
                  input:
                    type: object
                    properties:
                      lat:      { type: number }
                      lon:      { type: number }
                      mode:     { type: string }
                      extended: { type: array, items: { type: string } }
                      lang:     { type: string, nullable: true }
                  ms:  { type: number }
                  hit: { $ref: '#/components/schemas/Place' }
        '500':
          $ref: '#/components/responses/Error'

  /countries.php:
    get:
      summary: Liste aller Länder
      description: |
        Schlanke Liste aller GeoNames-Länder (252 Einträge), sortiert nach Name.
        Geeignet für Country-Picker/Dropdowns. Vollständige Detail-Felder gibt
        es einzeln über `/country.php?code=…`.
      parameters:
        - name: lang
          in: query
          description: ISO-Sprachcode für lokalisierten Landesnamen.
          schema: { type: string, example: "de" }
        - name: flag
          in: query
          description: "Wenn `1`: ergänzt `flag_url` pro Land."
          schema: { type: string, enum: ["0","1"], default: "0" }
      responses:
        '200':
          description: Trefferliste
          content:
            application/json:
              schema:
                type: object
                properties:
                  count:    { type: integer, example: 252 }
                  ms:       { type: number }
                  lang:     { type: string, nullable: true }
                  results:
                    type: array
                    items:
                      type: object
                      properties:
                        iso2:          { type: string, example: DE }
                        iso3:          { type: string, example: DEU }
                        name:          { type: string, example: Deutschland }
                        continent:     { type: string, example: EU }
                        currency_code: { type: string, example: EUR }
                        flag_url:      { type: string, format: uri, nullable: true }

  /country.php:
    get:
      summary: Detail zu einem Land
      description: Vollständige Country-Daten (currency, phone, postal, languages, neighbours, geonameid, …) plus optional die Liste der Admin1-Regionen.
      parameters:
        - name: code
          in: query
          required: true
          description: ISO-3166-1-Alpha-2-Code (z. B. `DE`, `at`).
          schema: { type: string, example: "DE" }
        - name: lang
          in: query
          schema: { type: string, example: "de" }
        - name: flag
          in: query
          schema: { type: string, enum: ["0","1"], default: "0" }
        - name: admin1
          in: query
          description: "Wenn `1`: ergänzt das Feld `admin1` mit allen Bundesländern/Kantonen/Provinzen."
          schema: { type: string, enum: ["0","1"], default: "0" }
      responses:
        '200':
          description: Country-Detail
          content:
            application/json:
              schema:
                type: object
                properties:
                  ms:      { type: number }
                  lang:    { type: string, nullable: true }
                  country:
                    allOf:
                      - $ref: '#/components/schemas/CountryDetail'
                      - type: object
                        properties:
                          admin1:
                            type: array
                            description: Nur bei `admin1=1` gesetzt.
                            items: { $ref: '#/components/schemas/Region' }
        '404':
          description: Land nicht gefunden
          content:
            application/json:
              schema:
                type: object
                properties:
                  error: { type: string }
                  code:  { type: string }

  /region.php:
    get:
      summary: Detail zu einer Admin1-Region
      description: |
        Bundesland/Kanton/Provinz, identifiziert über `country.admin1`-Code
        (z. B. `DE.02` für Bayern, `CH.ZH` für Zürich, `US.CA` für Kalifornien).
      parameters:
        - name: code
          in: query
          required: true
          description: Qualifizierter Code im Format `<ISO2>.<admin1>`.
          schema: { type: string, example: "DE.02" }
        - name: lang
          in: query
          schema: { type: string, example: "de" }
        - name: flag
          in: query
          description: "Wenn `1`: ergänzt `flag_url` (Flagge des übergeordneten Landes)."
          schema: { type: string, enum: ["0","1"], default: "0" }
      responses:
        '200':
          description: Region-Detail
          content:
            application/json:
              schema:
                type: object
                properties:
                  ms:     { type: number }
                  lang:   { type: string, nullable: true }
                  region: { $ref: '#/components/schemas/Region' }
        '404':
          description: Region nicht gefunden

  /flag.php:
    get:
      summary: Flagge eines Landes (302-Redirect oder JSON)
      description: |
        Default: `302 Location` zur SVG unter `/assets/Bilder/Flaggen/<iso2>.svg`.
        Mit `?as=json` antwortet der Endpoint stattdessen mit `{code, flag_url}`.
        404, wenn keine SVG-Datei für den Code vorliegt.
      parameters:
        - name: code
          in: query
          required: true
          schema: { type: string, example: "DE" }
        - name: as
          in: query
          description: "Wenn `json`: liefert JSON statt 302."
          schema: { type: string, enum: ["", "json"] }
      responses:
        '302':
          description: Redirect zur SVG-Datei
          headers:
            Location:
              schema: { type: string, format: uri }
        '200':
          description: JSON-Antwort (nur mit `?as=json`)
          content:
            application/json:
              schema:
                type: object
                properties:
                  code:     { type: string, example: DE }
                  flag_url: { type: string, format: uri }
        '404':
          description: Keine Flagge vorhanden

  /health.php:
    get:
      summary: Health-Check für Monitoring
      description: Liefert Status der API + Datenbank. UptimeRobot-kompatibel.
      responses:
        '200':
          description: API gesund
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:      { type: string, example: ok }
                  db_ok:       { type: boolean }
                  db_ms:       { type: integer }
                  php_version: { type: string }
                  duration_ms: { type: integer }
                  timestamp:   { type: string, format: date-time }
        '503':
          description: API teilweise gestört (DB nicht erreichbar)

components:
  schemas:
    Place:
      type: object
      description: |
        Standardfelder werden immer geliefert. Die Blöcke `country`, `admin1`,
        `admin2`, `timezone_info` und `flag_url` erscheinen nur, wenn
        `extended=1` bzw. `flag=1` angefragt wurde.
      properties:
        geonameid:     { type: integer, example: 2867714 }
        name:          { type: string,  example: München }
        asciiname:     { type: string,  example: Munich }
        country_code:  { type: string,  example: DE }
        admin1_code:   { type: string,  example: "02", nullable: true }
        admin2_code:   { type: string,  example: "091", nullable: true }
        feature_class: { type: string,  example: P, nullable: true }
        feature_code:  { type: string,  example: PPLA }
        population:    { type: integer, example: 1488202 }
        elevation:     { type: integer, nullable: true, description: Höhe in Meter (NN) }
        timezone:      { type: string,  example: Europe/Berlin }
        lat:           { type: number,  format: double, example: 48.13743 }
        lon:           { type: number,  format: double, example: 11.57549 }
        mod_date:      { type: string,  format: date, example: "2024-12-15", description: GeoNames-Stand der Zeile }
        distance_km:   { type: number,  format: double, nullable: true, description: nur bei lookup.php gesetzt }
        country:
          type: object
          nullable: true
          description: Nur bei `extended=1` gesetzt.
          properties:
            name:          { type: string }
            iso2:          { type: string, example: DE }
            iso3:          { type: string, example: DEU }
            iso_numeric:   { type: string, example: "276" }
            continent:     { type: string, example: EU }
            tld:           { type: string, example: .de }
            currency_code: { type: string, example: EUR }
            currency_name: { type: string, example: Euro }
            phone_prefix:  { type: string, example: "49" }
            population:    { type: integer }
            area_km2:      { type: integer }
            capital:       { type: string }
            languages:     { type: string, example: "de" }
            neighbours:    { type: string, example: "CH,PL,NL,DK,BE,FR,LU,CZ,AT" }
            postal_format: { type: string, example: "#####" }
            postal_regex:  { type: string }
        admin1:
          type: object
          nullable: true
          description: Bundesland/Region. Nur bei `extended=1` gesetzt.
          properties:
            code:       { type: string, example: "02" }
            name:       { type: string, example: Bavaria }
            name_ascii: { type: string, example: Bavaria }
            geonameid:  { type: integer, example: 2951839 }
        admin2:
          type: object
          nullable: true
          description: Kreis/District. Nur bei aktivem `admin`-Block gesetzt.
          properties:
            code: { type: string, example: "091" }
            name: { type: string, example: "Upper Bavaria" }
        timezone_info:
          type: object
          nullable: true
          description: Zeitzonen-Offsets in Stunden. Nur bei `extended=1` gesetzt.
          properties:
            tz_id:      { type: string, example: Europe/Berlin }
            gmt_offset: { type: number, format: double, example: 1.0 }
            dst_offset: { type: number, format: double, example: 2.0 }
            raw_offset: { type: number, format: double, example: 1.0 }
        flag_url:
          type: string
          format: uri
          nullable: true
          description: |
            Nur bei aktivem `flag`-Block gesetzt. Verweist auf die Länder-SVG-Flagge.
            `null`, wenn für den Country-Code keine SVG vorliegt.
          example: https://geoapi.world/assets/Bilder/Flaggen/de.svg
        localized_lang:
          type: string
          nullable: true
          description: |
            Nur bei aktivem `localized`-Block gesetzt — die ISO-Sprache, die für
            die `name`-Felder (Place, Country, Admin1) angewendet wurde.
          example: de

    CountryDetail:
      type: object
      description: Vollständige Country-Daten aus geo_countries.
      properties:
        iso2:          { type: string, example: DE }
        iso3:          { type: string, example: DEU }
        iso_numeric:   { type: string, example: "276", nullable: true }
        fips:          { type: string, example: GM,    nullable: true }
        name:          { type: string, example: Deutschland }
        capital:       { type: string, example: Berlin, nullable: true }
        area_km2:      { type: integer, example: 357021, nullable: true }
        population:    { type: integer, example: 82927922, nullable: true }
        continent:     { type: string, example: EU, nullable: true }
        tld:           { type: string, example: .de, nullable: true }
        currency_code: { type: string, example: EUR, nullable: true }
        currency_name: { type: string, example: Euro, nullable: true }
        phone_prefix:  { type: string, example: "49", nullable: true }
        postal_format: { type: string, example: "#####", nullable: true }
        postal_regex:  { type: string, nullable: true }
        languages:     { type: string, example: "de", nullable: true }
        neighbours:    { type: string, example: "CH,PL,NL,DK,BE,FR,LU,CZ,AT", nullable: true }
        geonameid:     { type: integer, example: 2921044, nullable: true }
        flag_url:      { type: string, format: uri, nullable: true }

    Region:
      type: object
      description: Admin1-Region (Bundesland/Kanton/Provinz).
      properties:
        code:         { type: string, example: "02", description: Reiner admin1-Code }
        qualified:    { type: string, example: "DE.02", description: ISO2.admin1 }
        country_code: { type: string, example: "DE" }
        country_name: { type: string, example: "Deutschland", nullable: true, description: Nur bei /region.php gesetzt }
        name:         { type: string, example: "Bayern" }
        name_ascii:   { type: string, example: "Bavaria" }
        geonameid:    { type: integer, example: 2951839 }
        flag_url:     { type: string, format: uri, nullable: true }

  responses:
    Error:
      description: Fehler
      content:
        application/json:
          schema:
            type: object
            properties:
              error: { type: string }

  securitySchemes:
    ApiKeyQuery:
      type: apiKey
      in: query
      name: key
