openapi: 3.1.0
info:
  title: haang public discovery API
  version: 1.0.0
  description: |
    Read-only HTTP surface for discovering **public** haang events and opening share links.
    Optional `Authorization: Bearer <JWT>` personalizes results when the user is signed in.

    **Limits:** discovery endpoints are rate-limited (~60 req/min per IP). Obvious scraper
    User-Agents (curl, python-requests, …) are blocked on unauthenticated discovery GETs.
    Use a normal HTTP client identity and stay within limits; contact support for partner access.

    Canonical human docs: https://haang.app/developers
  contact:
    name: haang support
    email: support@haang.app
  license:
    name: Proprietary
    url: https://haang.app/terms
servers:
  - url: https://api.haang.app
    description: Production API (set `EXPO_PUBLIC_API_URL` in clients)
  - url: http://localhost:8080
    description: Local API (`make local-api` / docker compose)
tags:
  - name: discovery
    description: Public event discovery (optional auth)
  - name: meta
    description: Health and taxonomy
  - name: sharing
    description: Link previews (HTML for crawlers)
paths:
  /health:
    get:
      tags: [meta]
      summary: Liveness probe
      operationId: health
      responses:
        '200':
          description: API is up
  /api/v1/events/nearby:
    get:
      tags: [discovery]
      summary: List public events near a point
      operationId: eventsNearby
      parameters:
        - $ref: '#/components/parameters/lat'
        - $ref: '#/components/parameters/lng'
        - name: radius_km
          in: query
          required: true
          schema:
            type: number
            minimum: 0
            maximum: 161
          description: Search radius in kilometers (app presets up to ~100 mi).
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: offset
          in: query
          schema:
            type: integer
            minimum: 0
            maximum: 1000
        - name: cursor
          in: query
          schema:
            type: string
          description: Opaque pagination cursor from a prior `nextCursor`.
        - name: q
          in: query
          schema:
            type: string
          description: Free-text search.
        - name: date_from
          in: query
          schema:
            type: string
            format: date
        - name: date_to
          in: query
          schema:
            type: string
            format: date
        - name: category
          in: query
          schema:
            type: string
        - name: sort
          in: query
          schema:
            type: string
            enum: [distance, date]
      security:
        - {}
        - bearerAuth: []
      responses:
        '200':
          description: Page of events
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NearbyResponse'
        '400':
          description: Invalid query
        '403':
          description: Blocked client or rate limited
  /api/v1/events/get:
    get:
      tags: [discovery]
      summary: Get one event by id
      operationId: eventsGet
      parameters:
        - name: id
          in: query
          required: true
          schema:
            type: string
            format: uuid
      security:
        - {}
        - bearerAuth: []
      responses:
        '200':
          description: Event detail (fields depend on privacy and viewer)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventResponse'
        '404':
          description: Not found or not visible to caller
  /api/v1/events/categories:
    get:
      tags: [meta]
      summary: Event category taxonomy
      operationId: eventsCategories
      responses:
        '200':
          description: Category tree for filters
  /event/{id}:
    get:
      tags: [sharing]
      summary: Share link preview (crawlers) or redirect (browsers)
      description: |
        Served on the API host. Social crawlers receive Open Graph HTML for **public** events.
        Normal browsers are redirected to `https://haang.app/event/{id}`.
      operationId: eventSharePreview
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: HTML with Open Graph tags (crawler)
        '302':
          description: Redirect to web app event URL
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  parameters:
    lat:
      name: lat
      in: query
      required: true
      schema:
        type: number
        format: double
    lng:
      name: lng
      in: query
      required: true
      schema:
        type: number
        format: double
  schemas:
    NearbyResponse:
      type: object
      required: [events, hasMore]
      properties:
        events:
          type: array
          items:
            $ref: '#/components/schemas/EventSummary'
        hasMore:
          type: boolean
        nextCursor:
          type: string
        scanLimited:
          type: boolean
    EventSummary:
      type: object
      description: Subset of fields returned in discovery lists; see GET /events/get for full detail.
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        startAt:
          type: string
          format: date-time
        privacy:
          type: string
          enum: [public, private]
        categories:
          type: array
          items:
            type: string
        locationLat:
          type: number
        locationLng:
          type: number
        shareUrl:
          type: string
          format: uri
          description: Canonical link — `https://haang.app/event/{id}`
      additionalProperties: true
    EventResponse:
      type: object
      description: Full event payload when the viewer is allowed to see it.
      additionalProperties: true
