openapi: 3.0.3
info:
  title: Vietnam Property Check — Public API
  description: >
    Read-only endpoints over Hanoi foreign-buyer property registration data.
    No authentication required for the endpoints listed here. Full sourcing,
    limitations, and why some fields are always null are documented at
    /methodology and docs/DATA_DICTIONARY.md in the repository.
  version: "1.0.0"
  contact:
    url: https://vnrealstate.homes/methodology
servers:
  - url: https://vnrealstate.homes
    description: Production
paths:
  /api/insights:
    get:
      summary: Aggregate registration statistics
      description: >
        Monthly registration counts, nationality distribution (overall and
        by year), district distribution, project ranking, and the
        unresolved-record rate. Computed live from the transactions and
        projects tables at request time. Never includes a foreign-quota
        fill percentage — see /methodology for why.
      operationId: getInsights
      responses:
        "200":
          description: Aggregate statistics
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Insights"
        "500":
          description: Failed to compute insights
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /api/projects/search:
    get:
      summary: Search projects by name or developer
      operationId: searchProjects
      parameters:
        - name: q
          in: query
          description: Search text (min 2 characters) matched against name_vn, name_en, developer
          schema:
            type: string
        - name: city
          in: query
          schema:
            type: string
            enum: [hanoi, hcmc, danang, other]
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
            maximum: 20
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        "200":
          description: Matching projects
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SearchResponse"
        "500":
          description: Search failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /api/check:
    post:
      summary: Look up what is on record for one project
      description: >
        Returns project-level fields where a source exists. Quota, permit,
        encumbrance, developer-risk, and pricing fields are almost always
        null in the current dataset (see docs/DATA_DICTIONARY.md) — this
        endpoint reports "unknown", it does not estimate or guess.
      operationId: checkProject
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [slug]
              properties:
                slug:
                  type: string
                  example: goldmark-city-hanoi
      responses:
        "200":
          description: Project check result
          content:
            application/json:
              schema:
                type: object
        "404":
          description: Project not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

components:
  schemas:
    Error:
      type: object
      properties:
        error:
          type: string

    MonthlyPoint:
      type: object
      properties:
        month:
          type: string
          example: "2025-03"
        count:
          type: integer

    NationalityCount:
      type: object
      properties:
        nationality:
          type: string
          example: Korean
        count:
          type: integer

    DistrictCount:
      type: object
      properties:
        district:
          type: string
        count:
          type: integer

    ProjectRanking:
      type: object
      properties:
        slug:
          type: string
        name:
          type: string
          nullable: true
        district:
          type: string
          nullable: true
        transactions_count:
          type: integer
        source_files_count:
          type: integer
        latest_contract_date:
          type: string
          format: date
          nullable: true
        data_completeness:
          type: string
          enum: [verified, partial, unknown]
        active_months:
          type: integer
          description: Distinct calendar months with at least one transaction
        avg_transactions_per_active_month:
          type: number
          nullable: true
          description: Observed average, not a projection

    Insights:
      type: object
      properties:
        generated_at:
          type: string
          format: date-time
        methodology_url:
          type: string
        summary:
          type: object
          properties:
            total_transactions:
              type: integer
            total_projects:
              type: integer
            unresolved_count:
              type: integer
            unresolved_rate:
              type: number
              nullable: true
              description: Percentage, 0-100
            earliest_contract_date:
              type: string
              format: date
              nullable: true
            latest_contract_date:
              type: string
              format: date
              nullable: true
        monthly:
          type: array
          items:
            $ref: "#/components/schemas/MonthlyPoint"
        nationalities:
          type: array
          items:
            $ref: "#/components/schemas/NationalityCount"
        nationalities_by_year:
          type: array
          items:
            type: object
            properties:
              year:
                type: string
              breakdown:
                type: array
                items:
                  $ref: "#/components/schemas/NationalityCount"
        districts:
          type: array
          items:
            $ref: "#/components/schemas/DistrictCount"
        projects:
          type: array
          items:
            $ref: "#/components/schemas/ProjectRanking"

    SearchResponse:
      type: object
      properties:
        results:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
              slug:
                type: string
              name_vn:
                type: string
              name_en:
                type: string
                nullable: true
              developer:
                type: string
                nullable: true
              city:
                type: string
              district:
                type: string
                nullable: true
              lat:
                type: number
                nullable: true
              lng:
                type: number
                nullable: true
        total:
          type: integer
        query:
          type: string
