openapi: 3.1.0
info:
  title: Pattaya Gym Directory API
  description: |
    Read-only access to 218 published, source-bounded Pattaya.Gym directory records across gyms,
    Muay Thai camps, sport clubs, facilities and operators. Records expose their
    sources-reviewed date; published price snapshots carry a separate as-of date.
    No field implies a first-hand visit. Use is subject to the Pattaya.Gym Terms of Use.
  version: 1.1.0
  contact:
    email: info@pattaya-gym.com
    url: https://pattaya-gym.com/
  license:
    name: Pattaya.Gym Terms of Use
    url: https://pattaya-gym.com/terms/
servers:
  - url: https://pattaya-gym.com
    description: Production
paths:
  /api/venues.json:
    get:
      operationId: listVenues
      summary: Get the published Pattaya.Gym venue-record dataset
      responses:
        '200':
          description: Published source-bounded directory records, including visible status warnings and unknowns
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Directory'
  /api/venues.csv:
    get:
      operationId: downloadVenuesCsv
      summary: Download a flat, source-bounded venue-record table
      description: |
        One stable row per published directory record. Cells whose first
        character is =, +, - or @ are prefixed with an apostrophe to prevent
        spreadsheet formula execution; use /api/venues.json for unmodified values.
      responses:
        '200':
          description: UTF-8 CSV with a header row and spreadsheet-safe cell escaping
          content:
            text/csv:
              schema:
                type: string
  /data/directory-index/summary.json:
    get:
      operationId: getDirectoryIndexSummary
      summary: Get deterministic directory counts and the CSV data fingerprint
      description: |
        Evidence-dated aggregate counts, category and area breakdowns, and the
        SHA-256 fingerprint of the exact /api/venues.csv bytes. Counts describe
        published records, including records with visible status warnings.
      responses:
        '200':
          description: Deterministic source-bounded directory summary
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DirectoryIndexSummary'
components:
  schemas:
    Directory:
      type: object
      required: [name, dataModified, counts, venues]
      properties:
        name: { type: string }
        url: { type: string, format: uri }
        description: { type: string }
        dataModified:
          type: string
          format: date
          description: Newest stored source-review or price-evidence date represented by the endpoint
        license: { type: string }
        termsUrl: { type: string, format: uri }
        attribution: { type: string }
        counts:
          type: object
          properties:
            venues: { type: integer, example: 218 }
            categories: { type: integer }
        categories:
          type: array
          items: { $ref: '#/components/schemas/Category' }
        venues:
          type: array
          items: { $ref: '#/components/schemas/Venue' }
    Category:
      type: object
      properties:
        key: { type: string, example: muay-thai }
        label: { type: string, example: Muay Thai }
    DirectoryIndexSummary:
      type: object
      required: [schemaVersion, name, url, canonicalDataset, dataModified, dataState, counts]
      properties:
        schemaVersion: { type: integer, example: 1 }
        name: { type: string }
        url: { type: string, format: uri }
        canonicalDataset: { type: string, format: uri }
        dataModified:
          type: string
          format: date
          description: Newest stored source-review or price-evidence date represented by the summary
        dataState:
          type: object
          required: [sha256, algorithm, basis]
          properties:
            sha256: { type: string, pattern: '^[a-f0-9]{64}$' }
            shortFingerprint: { type: string }
            algorithm: { type: string, const: SHA-256 }
            basis: { type: string }
        counts:
          type: object
          required: [records, sportCategories]
          properties:
            records: { type: integer, example: 218 }
            sportCategories: { type: integer }
            sourceAreaLabels: { type: integer }
            recordsWithPriceRange: { type: integer }
            recordsWithDatedPriceSource: { type: integer }
    Venue:
      type: object
      required: [id, name, url, category]
      properties:
        id: { type: string }
        name: { type: string }
        url: { type: string, format: uri }
        category: { type: string }
        categoryLabel: { type: string }
        area: { type: [string, 'null'] }
        address: { type: [string, 'null'] }
        phone: { type: [string, 'null'] }
        website: { type: [string, 'null'], format: uri }
        hours: { type: [string, 'null'] }
        priceRange: { type: [string, 'null'] }
        priceAsOf: { type: [string, 'null'], format: date }
        priceSourceUrl: { type: [string, 'null'], format: uri }
        description: { type: [string, 'null'] }
        tags: { type: array, items: { type: string } }
        mapsUrl: { type: [string, 'null'], format: uri }
        status: { type: [string, 'null'] }
        sourcesReviewed: { type: [string, 'null'], format: date }
