openapi: 3.1.0
info:
  title: Wilayah Indonesia API
  description: |
    API statis data wilayah Indonesia — 38 Provinsi, 514 Kab/Kota,
    7.285 Kecamatan, 83.762 Kelurahan + kode pos & polygon.
    Di-generate oleh tools/generate_static_api.go (genResponse { data, meta, level }).
    Static hosting di GitHub Pages via www.emsifa.com, tanpa API key (CORS enabled).
  version: 2.0.0
  contact:
    name: emsifa
    url: https://github.com/emsifa/api-wilayah-indonesia
  license:
    name: MIT
servers:
  - url: https://www.emsifa.com/api-wilayah-indonesia/v2
    description: Production (www.emsifa.com — custom domain GitHub Pages, CORS enabled)

paths:
  /stats.json:
    get:
      summary: Total wilayah
      description: Mengambil total provinsi, kab/kota, kecamatan, kelurahan, kode pos, luas, populasi
      operationId: getStats
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StatsResponse"
              example:
                data:
                  total_area: 1889518.254
                  total_disk_usage: 915771392
                  total_disk_usage_human: "873.35 MB"
                  total_districts: 7285
                  total_endpoints: 201309
                  total_filesize: 300423246
                  total_filesize_human: "286.51 MB"
                  total_paths: 91238
                  total_population: 284973643
                  total_postal_codes: 10632
                  total_provinces: 38
                  total_regencies: 514
                  total_villages: 83762
                meta: { updated_at: "2026-09-04", level: 0 }

  /provinces.json:
    get:
      summary: List provinsi
      description: Daftar semua provinsi (level 1) dengan capital, lat, lng, elv, tz, population, total_area, has_path
      operationId: listProvinces
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlacesResponse"

  /provinces/{code}.json:
    get:
      summary: Detail provinsi
      parameters:
        - name: code
          in: path
          required: true
          schema: { type: string, example: "32" }
          description: Kode provinsi 2 digit (contoh 32 = Jawa Barat)
      operationId: getProvince
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlaceResponse"
        "404":
          description: Not Found

  /regencies/{province_code}.json:
    get:
      summary: List kab/kota by provinsi
      parameters:
        - name: province_code
          in: path
          required: true
          schema: { type: string, example: "32" }
      operationId: listRegenciesByProvince
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlacesResponse"

  /regencies/{regency_code}.json:
    get:
      summary: Detail kab/kota
      parameters:
        - name: regency_code
          in: path
          required: true
          schema: { type: string, example: "32.73" }
      operationId: getRegency
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlaceResponse"

  /districts/{regency_code}.json:
    get:
      summary: List kecamatan by kab/kota
      description: Mengembalikan genShortItem[] (id, name, has_path, lat, lng)
      parameters:
        - name: regency_code
          in: path
          required: true
          schema: { type: string, example: "32.73" }
      operationId: listDistrictsByRegency
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ShortItemsResponse"

  /districts/{district_code}.json:
    get:
      summary: Detail kecamatan
      parameters:
        - name: district_code
          in: path
          required: true
          schema: { type: string, example: "32.73.01" }
      operationId: getDistrict
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlaceResponse"

  /villages/{district_code}.json:
    get:
      summary: List kelurahan by kecamatan
      description: genShortItem[] dengan postal_code, has_path, lat, lng
      parameters:
        - name: district_code
          in: path
          required: true
          schema: { type: string, example: "32.73.01" }
      operationId: listVillagesByDistrict
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ShortItemsResponse"

  /villages/{village_code}.json:
    get:
      summary: Detail kelurahan
      parameters:
        - name: village_code
          in: path
          required: true
          schema: { type: string, example: "32.73.01.1001" }
      operationId: getVillage
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlaceResponse"

  /postal-codes/{postal_code}.json:
    get:
      summary: Cari kelurahan by kode pos
      parameters:
        - name: postal_code
          in: path
          required: true
          schema: { type: string, example: "40152" }
      operationId: listByPostalCode
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlacesResponse"

  /paths/{code}.json:
    get:
      summary: Polygon provinsi/kab-kota/kec/kelurahan
      description: "Compact JSON { id, name, path: number[][] } — level = genLevelCount(code)+1, tersedia jika has_path=true pada endpoint wilayah"
      parameters:
        - name: code
          in: path
          required: true
          schema: { type: string, example: "32" }
      operationId: getPath
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PathResponse"

  /missings.json:
    get:
      summary: Wilayah belum lengkap
      description: List wilayah dimana has_path==false OR has_latlng==false (path atau koordinat belum tersedia). Setiap item {id, name, has_path, has_latlng}
      operationId: getMissings
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MissingsResponse"
              example:
                data:
                  - { id: "11.16.06.2021", name: "Alur Mentawak", has_path: false, has_latlng: false }
                  - { id: "11.16.08.2017", name: "Mekar Jaya", has_path: false, has_latlng: false }
                meta: { updated_at: "2026-09-04", level: 0 }
                summary: { total_missing: 361, total_missing_path: 361, total_missing_latlng: 359, total_missing_both: 359, by_level: { province: 0, regency: 1, district: 0, village: 360 } }

components:
  schemas:
    Meta:
      type: object
      required: [updated_at, level]
      properties:
        updated_at: { type: string, format: date, example: "2026-09-04" }
        level: { type: integer, example: 1, description: "0=stats, 1=prov, 2=reg, 3=dist, 4=vill/postal" }

    Place:
      type: object
      required: [id, name, has_path]
      properties:
        id: { type: string, example: "32.73.01.1001" }
        name: { type: string, example: "Sukarasa" }
        capital: { type: string, example: "Bandung" }
        lat: { type: number, example: -6.90 }
        lng: { type: number, example: 107.61 }
        elv: { type: number, example: 739 }
        tz: { type: integer, example: 7 }
        population: { type: integer, example: 51316378 }
        total_area: { type: number, example: 37053.33 }
        postal_code: { type: string, example: "40152" }
        has_path: { type: boolean, example: true, description: "true jika /paths/{id}.json tersedia" }
        province: { $ref: "#/components/schemas/ShortItem" }
        regency: { $ref: "#/components/schemas/ShortItem" }
        district: { $ref: "#/components/schemas/ShortItem" }

    ShortItem:
      type: object
      required: [id, name, has_path]
      properties:
        id: { type: string, example: "32.73.01" }
        name: { type: string, example: "Sukasari" }
        postal_code: { type: string, example: "40152" }
        has_path: { type: boolean, example: true, description: "true jika /paths/{id}.json tersedia" }
        lat: { type: number, example: -6.90 }
        lng: { type: number, example: 107.61 }

    PlacesResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items: { $ref: "#/components/schemas/Place" }
        meta: { $ref: "#/components/schemas/Meta" }

    PlaceResponse:
      type: object
      required: [data, meta]
      properties:
        data: { $ref: "#/components/schemas/Place" }
        meta: { $ref: "#/components/schemas/Meta" }

    ShortItemsResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: array
          items: { $ref: "#/components/schemas/ShortItem" }
        meta: { $ref: "#/components/schemas/Meta" }

    StatsResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: object
          properties:
            total_area: { type: number, example: 1889518.254 }
            total_disk_usage: { type: integer, example: 915771392 }
            total_disk_usage_human: { type: string, example: "873.35 MB" }
            total_districts: { type: integer, example: 7285 }
            total_endpoints: { type: integer, example: 201309 }
            total_filesize: { type: integer, example: 300423246 }
            total_filesize_human: { type: string, example: "286.51 MB" }
            total_paths: { type: integer, example: 91238 }
            total_population: { type: integer, example: 284973643 }
            total_postal_codes: { type: integer, example: 10632 }
            total_provinces: { type: integer, example: 38 }
            total_regencies: { type: integer, example: 514 }
            total_villages: { type: integer, example: 83762 }
        meta: { $ref: "#/components/schemas/Meta" }

    PathResponse:
      type: object
      required: [data, meta]
      properties:
        data:
          type: object
          required: [id, name, path]
          properties:
            id: { type: string, example: "32" }
            name: { type: string, example: "Jawa Barat" }
            path:
              type: array
              description: Polygon coords [lat, lng][]
              items:
                type: array
                items: { type: number }
                example: [-6.98, 106.39]
        meta: { $ref: "#/components/schemas/Meta" }

    MissingItem:
      type: object
      required: [id, name, has_path, has_latlng]
      properties:
        id: { type: string, example: "53.09.14.2011" }
        name: { type: string, example: "Watu Pangan" }
        has_path: { type: boolean, example: false, description: "true jika /paths/{id}.json ada" }
        has_latlng: { type: boolean, example: false, description: "true jika lat & lng ada dan tidak 0/null" }

    MissingsResponse:
      type: object
      required: [data, meta, summary]
      properties:
        data:
          type: array
          items: { $ref: "#/components/schemas/MissingItem" }
        meta: { $ref: "#/components/schemas/Meta" }
        summary:
          type: object
          required: [total_missing, total_missing_path, total_missing_latlng, total_missing_both, by_level]
          properties:
            total_missing: { type: integer, example: 361 }
            total_missing_path: { type: integer, example: 361 }
            total_missing_latlng: { type: integer, example: 359 }
            total_missing_both: { type: integer, example: 359 }
            by_level:
              type: object
              properties:
                province: { type: integer, example: 0 }
                regency: { type: integer, example: 1 }
                district: { type: integer, example: 0 }
                village: { type: integer, example: 360 }
