# GridRock OpenAPI 3.1 Specification
# Single source of truth for /docs/reference, llms.txt, MCP manifest, and CI manifest validation.
# Hosted at:
#   https://api.gridrock.ai/openapi.yaml   (canonical)
#   https://gridrock.ai/openapi.yaml       (mirror; for SEO + crawler discovery)
#
# Custom extensions:
#   x-llm-friendly       — true if the operation is safe for LLM tool-use.
#   x-tokens-cost        — coarse LLM-token cost of a typical response.
#   x-typical-use-case   — natural-language hint for an LLM agent.
#   x-related-endpoints  — list of operationIds an agent should chain.
openapi: 3.1.0
info:
  title: GridRock — Data Intelligence Layer of Bharat
  version: 1.0.0
  summary: First-party Indian geospatial + civic + environment + POI + transit intelligence, agent-native.
  description: |
    GridRock is the substrate that makes every 100m hex of India queryable for humans, developers, machines, and autonomous LLM agents.

    Free tier:
      - `free_human` — 5 req/min, 1k req/day, `gr_free_*` API key.
      - `free_agent` — 60 req/min, 5k req/day, `agtok_*` Bearer JWT (RFC 7591 self-onboarded).

    Both tiers share the same handlers and endpoints; agent-ness is a tier + an `Accept: application/vnd.gridrock.agent+json` response shape.
  termsOfService: https://gridrock.ai/legal/tos
  contact:
    name: GridRock Platform Eng
    url: https://gridrock.ai/about
    email: hello@gridrock.ai
  license:
    name: GridRock API Terms
    url: https://gridrock.ai/legal/tos
servers:
  - url: https://api.gridrock.ai
    description: Production paid + agent surface
  - url: https://free.gridrock.ai
    description: Free tier (CloudFront-fronted, edge-cached)

tags:
  - name: geo
    description: Lat/lng ↔ H3 ↔ admin primitives.
  - name: hex
    description: H3 cell math, semantic labels, freshness.
  - name: meta
    description: Service catalogs (cities, signals, freshness, health).
  - name: env
    description: Environment signals (AQI, weather, forecast).
  - name: admin
    description: Administrative-boundary lookups (ward, pincode, state, city).
  - name: transit
    description: Transit stops + routes (BMTC, BEST, Metro, Mono, Suburban).
  - name: poi
    description: Points of Interest (Google Places-derived).
  - name: ref
    description: Reference catalogs (states, holidays, elections, languages).
  - name: intel
    description: Combined showcase endpoints powering /intelligence/* on gridrock.ai.
  - name: oauth
    description: RFC 7591 dynamic client registration + OAuth 2.1 client_credentials.

components:
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: |
        `gr_free_*` for the free tier (issued at https://gridrock.ai/start);
        `gr_live_*` for paid (Console → Keys).
    agentBearer:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        `agtok_*` short-lived JWT (1h TTL) obtained via `POST /oauth/token`
        with `grant_type=client_credentials` using an `agt_*/agtsec_*` pair
        registered via `POST /oauth/register` (RFC 7591).
  parameters:
    Lat:
      name: lat
      in: query
      required: true
      description: Latitude in degrees [-90, 90]; rounded to 5dp for cache friendliness.
      schema: { type: number, format: double, minimum: -90, maximum: 90, example: 19.0760 }
    Lng:
      name: lng
      in: query
      required: true
      description: Longitude in degrees [-180, 180]; rounded to 5dp for cache friendliness.
      schema: { type: number, format: double, minimum: -180, maximum: 180, example: 72.8777 }
    H3:
      name: h3
      in: path
      required: true
      description: H3 cell index (15 or 16 hex chars).
      schema: { type: string, pattern: '^[0-9a-fA-F]{15,16}$', example: '892a100c87ffff' }
    Resolution:
      name: res
      in: query
      required: false
      description: H3 resolution [0, 15]; default 9 (~150m edge).
      schema: { type: integer, minimum: 0, maximum: 15, default: 9 }
  responses:
    Error:
      description: Structured error (agent-native shape).
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
  schemas:
    Error:
      type: object
      required: [error, message, next_action, docs]
      properties:
        error: { type: string, description: Stable machine code, never localised. }
        message: { type: string }
        field: { type: string, nullable: true }
        received: { description: Offending input, nullable: true }
        valid_range: { description: Acceptable range, enum, or regex., nullable: true }
        next_action:
          type: string
          enum: [retry_with_corrected_field, retry_after_seconds, upgrade_tier, register, no_retry]
        retry_after_seconds: { type: integer, nullable: true }
        example: { type: string, nullable: true }
        docs: { type: string, format: uri }
    LatLng:
      type: object
      properties:
        lat: { type: number, format: double }
        lng: { type: number, format: double }

security:
  - apiKey: []
  - agentBearer: []

paths:
  /v1/geo/h3:
    get:
      operationId: geoLatLngToH3
      summary: Resolve lat/lng to an H3 cell index.
      tags: [geo]
      x-llm-friendly: true
      x-tokens-cost: 50
      x-typical-use-case: "Convert a GPS coordinate to a stable spatial key for joining."
      x-related-endpoints: [hexBoundary, hexLabels]
      parameters:
        - $ref: '#/components/parameters/Lat'
        - $ref: '#/components/parameters/Lng'
        - $ref: '#/components/parameters/Resolution'
      responses:
        '200':
          description: H3 cell.
          content:
            application/json:
              schema:
                type: object
                properties:
                  lat: { type: number }
                  lng: { type: number }
                  resolution: { type: integer }
                  h3: { type: string }
        default: { $ref: '#/components/responses/Error' }

  /v1/geo/reverse:
    get:
      operationId: geoReverse
      summary: Reverse-geocode lat/lng to city + semantic labels.
      tags: [geo]
      x-llm-friendly: true
      x-tokens-cost: 600
      x-typical-use-case: "What is at this coordinate?"
      x-related-endpoints: [intelHex, hexLabels]
      parameters:
        - $ref: '#/components/parameters/Lat'
        - $ref: '#/components/parameters/Lng'
        - $ref: '#/components/parameters/Resolution'
      responses:
        '200': { description: Reverse geocode + label rollup. }
        default: { $ref: '#/components/responses/Error' }

  /v1/geo/forward:
    get:
      operationId: geoForward
      summary: Resolve a place name to a city centroid.
      tags: [geo]
      x-llm-friendly: true
      x-tokens-cost: 800
      parameters:
        - { name: q, in: query, required: true, schema: { type: string, minLength: 2 } }
        - { name: country, in: query, schema: { type: string, pattern: '^[A-Z]{2}$' } }
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 25, default: 10 } }
      responses:
        '200': { description: Ranked candidate list. }
        default: { $ref: '#/components/responses/Error' }

  /v1/geo/autocomplete:
    get:
      operationId: geoAutocomplete
      summary: Prefix-match city names for typeahead.
      tags: [geo]
      x-llm-friendly: false
      x-tokens-cost: 400
      parameters:
        - { name: q, in: query, required: true, schema: { type: string, minLength: 1 } }
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 25, default: 10 } }
      responses:
        '200': { description: Suggestions. }
        default: { $ref: '#/components/responses/Error' }

  /v1/hex/{h3}/boundary:
    get:
      operationId: hexBoundary
      summary: Get the polygon vertices of an H3 cell.
      tags: [hex]
      x-llm-friendly: true
      x-tokens-cost: 250
      parameters: [{ $ref: '#/components/parameters/H3' }]
      responses:
        '200': { description: Vertices + center. }
        default: { $ref: '#/components/responses/Error' }

  /v1/hex/{h3}/parent:
    get:
      operationId: hexParent
      summary: Get the parent H3 cell at a coarser resolution.
      tags: [hex]
      parameters:
        - { $ref: '#/components/parameters/H3' }
        - $ref: '#/components/parameters/Resolution'
      responses: { '200': { description: Parent cell. }, default: { $ref: '#/components/responses/Error' } }

  /v1/hex/{h3}/children:
    get:
      operationId: hexChildren
      summary: Get child H3 cells at a finer resolution (max +3 levels).
      tags: [hex]
      parameters:
        - { $ref: '#/components/parameters/H3' }
        - $ref: '#/components/parameters/Resolution'
      responses: { '200': { description: Child cells. }, default: { $ref: '#/components/responses/Error' } }

  /v1/hex/{h3}/neighbors:
    get:
      operationId: hexNeighbors
      summary: Get k-ring neighbour cells.
      tags: [hex]
      parameters:
        - { $ref: '#/components/parameters/H3' }
        - { name: k, in: query, schema: { type: integer, minimum: 1, maximum: 5, default: 1 } }
      responses: { '200': { description: Neighbour cells. }, default: { $ref: '#/components/responses/Error' } }

  /v1/hex/{h3}/labels:
    get:
      operationId: hexLabels
      summary: Get semantic labels (locality, ward, LULC, etc.) for a cell.
      tags: [hex]
      x-llm-friendly: true
      x-tokens-cost: 700
      parameters: [{ $ref: '#/components/parameters/H3' }]
      responses: { '200': { description: Labels. }, default: { $ref: '#/components/responses/Error' } }

  /v1/hex/{h3}/freshness:
    get:
      operationId: hexFreshness
      summary: Get signal freshness state for a cell.
      tags: [hex]
      parameters: [{ $ref: '#/components/parameters/H3' }]
      responses: { '200': { description: Freshness rows. }, default: { $ref: '#/components/responses/Error' } }

  /v1/meta/cities:
    get:
      operationId: metaCities
      summary: List all onboarded cities and their bboxes.
      tags: [meta]
      x-llm-friendly: true
      x-tokens-cost: 1500
      responses: { '200': { description: Cities. } }

  /v1/meta/signals:
    get:
      operationId: metaSignals
      summary: List the catalogue of signal types and their sample frequencies.
      tags: [meta]
      responses: { '200': { description: Signals catalog. } }

  /v1/meta/health:
    get:
      operationId: metaHealth
      summary: Service identity + uptime.
      tags: [meta]
      responses: { '200': { description: Health. } }

  /v1/env/aqi:
    get:
      operationId: envAqi
      summary: Latest air-quality reading at lat/lng.
      tags: [env]
      x-llm-friendly: true
      x-tokens-cost: 300
      parameters:
        - $ref: '#/components/parameters/Lat'
        - $ref: '#/components/parameters/Lng'
      responses: { '200': { description: AQI snapshot. } }

  /v1/env/weather:
    get:
      operationId: envWeather
      summary: Latest weather observation at lat/lng.
      tags: [env]
      parameters:
        - $ref: '#/components/parameters/Lat'
        - $ref: '#/components/parameters/Lng'
      responses: { '200': { description: Weather snapshot. } }

  /v1/env/forecast:
    get:
      operationId: envForecast
      summary: 24-hour weather forecast at lat/lng.
      tags: [env]
      parameters:
        - $ref: '#/components/parameters/Lat'
        - $ref: '#/components/parameters/Lng'
        - { name: hours, in: query, schema: { type: integer, minimum: 1, maximum: 24, default: 24 } }
      responses: { '200': { description: Forecast frames. } }

  /v1/admin/ward:
    get:
      operationId: adminWard
      summary: Get the enclosing municipal ward polygon.
      tags: [admin]
      x-llm-friendly: true
      x-tokens-cost: 250
      parameters:
        - $ref: '#/components/parameters/Lat'
        - $ref: '#/components/parameters/Lng'
        - { name: layer, in: query, schema: { type: string, enum: [bmc_ward, pmc_ward, mcd_ward, bbmp_ward, kmc_ward, gcc_ward, ghmc_ward] } }
      responses: { '200': { description: Ward or available:false. } }

  /v1/admin/pincode:
    get:
      operationId: adminPincode
      summary: Get the enclosing pincode polygon.
      tags: [admin]
      parameters:
        - $ref: '#/components/parameters/Lat'
        - $ref: '#/components/parameters/Lng'
      responses: { '200': { description: Pincode. } }

  /v1/admin/state:
    get:
      operationId: adminState
      summary: Get the enclosing state.
      tags: [admin]
      parameters:
        - $ref: '#/components/parameters/Lat'
        - $ref: '#/components/parameters/Lng'
      responses: { '200': { description: State. } }

  /v1/admin/city:
    get:
      operationId: adminCity
      summary: Get the enclosing onboarded city.
      tags: [admin]
      parameters:
        - $ref: '#/components/parameters/Lat'
        - $ref: '#/components/parameters/Lng'
      responses: { '200': { description: City. } }

  /v1/transit/nearest:
    get:
      operationId: transitNearest
      summary: Find nearest transit stops within a radius.
      tags: [transit]
      x-llm-friendly: true
      x-tokens-cost: 600
      parameters:
        - $ref: '#/components/parameters/Lat'
        - $ref: '#/components/parameters/Lng'
        - { name: mode, in: query, schema: { type: string, enum: [bus, metro, train, tram, ferry, mono] } }
        - { name: radius_m, in: query, schema: { type: integer, minimum: 1, maximum: 5000, default: 1000 } }
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 25, default: 10 } }
      responses: { '200': { description: Stops. } }

  /v1/transit/stops:
    get:
      operationId: transitStops
      summary: List transit stops by city or H3 cell.
      tags: [transit]
      parameters:
        - { name: city_id, in: query, schema: { type: integer } }
        - { name: h3, in: query, schema: { type: string } }
        - { name: mode, in: query, schema: { type: string } }
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 200, default: 50 } }
      responses: { '200': { description: Stops. } }

  /v1/transit/routes:
    get:
      operationId: transitRoutes
      summary: List distinct agencies/routes in a city.
      tags: [transit]
      parameters:
        - { name: city_id, in: query, required: true, schema: { type: integer } }
        - { name: agency, in: query, schema: { type: string } }
      responses: { '200': { description: Agencies. } }

  /v1/poi/nearby:
    get:
      operationId: poiNearby
      summary: Find points of interest within a radius (free-tier hard caps).
      tags: [poi]
      x-llm-friendly: true
      x-tokens-cost: 1500
      parameters:
        - $ref: '#/components/parameters/Lat'
        - $ref: '#/components/parameters/Lng'
        - { name: radius_m, in: query, schema: { type: integer, minimum: 1, maximum: 1000, default: 500 } }
        - { name: type, in: query, schema: { type: string, description: 'Comma-separated list, max 60.' } }
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 20, default: 10 } }
      responses: { '200': { description: POIs. } }

  /v1/poi/details/{place_id}:
    get:
      operationId: poiDetails
      summary: Full POI record by place_id.
      tags: [poi]
      parameters:
        - { name: place_id, in: path, required: true, schema: { type: string } }
      responses: { '200': { description: POI. } }

  /v1/poi/categories:
    get:
      operationId: poiCategories
      summary: POI taxonomy.
      tags: [poi]
      responses: { '200': { description: Categories. } }

  /v1/ref/cities:
    get: { operationId: refCities, summary: Reference cities catalog., tags: [ref], responses: { '200': { description: OK. } } }
  /v1/ref/states:
    get: { operationId: refStates, summary: India states catalog (ISO 3166-2:IN)., tags: [ref], responses: { '200': { description: OK. } } }
  /v1/ref/pincodes:
    get:
      operationId: refPincodes
      summary: Pincode prefix lookup.
      tags: [ref]
      parameters:
        - { name: prefix, in: query, required: true, schema: { type: string, pattern: '^[0-9]{2,6}$' } }
      responses: { '200': { description: OK. } }
  /v1/ref/holidays:
    get:
      operationId: refHolidays
      summary: India national holidays catalog.
      tags: [ref]
      parameters:
        - { name: country, in: query, schema: { type: string, default: IN } }
        - { name: year, in: query, schema: { type: integer, minimum: 2025, maximum: 2027 } }
      responses: { '200': { description: OK. } }
  /v1/ref/elections:
    get: { operationId: refElections, summary: Upcoming Indian elections., tags: [ref], responses: { '200': { description: OK. } } }
  /v1/ref/languages:
    get: { operationId: refLanguages, summary: Indian languages catalog., tags: [ref], responses: { '200': { description: OK. } } }

  /v1/intel/hex/{h3}:
    get:
      operationId: intelHex
      summary: Single-hex full-stack readout (showcase endpoint).
      description: |
        The endpoint that powers every `/intelligence/hex/{h3}` showcase
        page on gridrock.ai. One call returns: cell math + boundary +
        labels (32) + freshness (16) + admin polygons + nearest transit (5)
        + top POIs (10) + 3 environment signals.
      tags: [intel]
      x-llm-friendly: true
      x-tokens-cost: 3500
      x-typical-use-case: "Full ground-truth context for a single 100m hex of India."
      x-related-endpoints: [hexBoundary, hexLabels, transitNearest, poiNearby, envAqi]
      parameters: [{ $ref: '#/components/parameters/H3' }]
      responses: { '200': { description: Combined readout. } }

  /oauth/register:
    post:
      operationId: oauthRegister
      summary: RFC 7591 dynamic client registration (agents).
      tags: [oauth]
      x-llm-friendly: true
      x-tokens-cost: 200
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [client_name, tos_accepted, intended_use, contact_email]
              properties:
                client_name: { type: string }
                client_uri: { type: string, format: uri }
                tos_accepted: { type: boolean }
                intended_use: { type: string, enum: [research, product, bot, automation] }
                contact_email: { type: string, format: email }
      responses:
        '201': { description: Created. }
        default: { $ref: '#/components/responses/Error' }

  /oauth/token:
    post:
      operationId: oauthToken
      summary: OAuth 2.1 client_credentials → short-lived JWT.
      tags: [oauth]
      x-llm-friendly: true
      x-tokens-cost: 100
      security: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [grant_type, client_id, client_secret]
              properties:
                grant_type: { type: string, enum: [client_credentials] }
                client_id: { type: string }
                client_secret: { type: string }
                scope: { type: string }
      responses:
        '200':
          description: Bearer token.
          content:
            application/json:
              schema:
                type: object
                properties:
                  access_token: { type: string }
                  token_type: { type: string, enum: [Bearer] }
                  expires_in: { type: integer }
                  scope: { type: string }
        default: { $ref: '#/components/responses/Error' }
