{
  "openapi": "3.1.1",
  "info": {
    "title": "sfclarity public events API",
    "version": "1.0.0",
    "summary": "Read-only access to sfclarity's SF tech-events dataset.",
    "description": "sfclarity is a public directory of San Francisco tech events. This document describes the two read-only endpoints the site's own frontend calls: the published event rows, and a small landing-page stats view. Both are served directly by Supabase PostgREST (https://postgrest.org) under Row Level Security scoped to the `published` publishable key below — that key can only ever read rows with `published = true`, and cannot write, regardless of the query sent. There is no write access, no account or sign-in needed, and no endpoint here needs a secret key. For itinerary composition (a separate, stateless POST endpoint) see https://sfclarity.com/ — it is not part of this read surface.",
    "contact": { "url": "https://sfclarity.com" },
    "license": { "name": "Proprietary — source repository is private" }
  },
  "servers": [
    {
      "url": "https://bfyqojtqsflybyizluot.supabase.co/rest/v1",
      "description": "Supabase PostgREST (production)"
    }
  ],
  "security": [{ "publishableKey": [] }],
  "paths": {
    "/events_enriched": {
      "get": {
        "operationId": "listEvents",
        "summary": "List published SF tech events",
        "description": "Rows from the AI-enriched, geocoded events dataset. Row Level Security restricts every request made with the publishable key to `published = true` rows, so filtering on `published` is optional (kept in examples below because it is what the frontend itself sends). PostgREST query syntax: https://docs.postgrest.org/en/v12/references/api/tables_views.html",
        "parameters": [
          {
            "name": "select",
            "in": "query",
            "description": "Comma-separated column list. Omit for all columns.",
            "schema": { "type": "string" },
            "example": "title,date_start_iso,date_end_iso,venue,image_url,hosts,summary_ai_enriched,coordinates,url_sanitized_resolved"
          },
          {
            "name": "published",
            "in": "query",
            "description": "PostgREST filter syntax `eq.<value>`. RLS already restricts every response to true regardless of this filter.",
            "schema": { "type": "string", "enum": ["eq.true"] }
          },
          {
            "name": "date_start_iso",
            "in": "query",
            "description": "PostgREST filter syntax, e.g. `gte.2026-09-01`.",
            "schema": { "type": "string" }
          },
          {
            "name": "order",
            "in": "query",
            "description": "PostgREST order syntax: `<column>.<asc|desc>[.nullsfirst|.nullslast]`.",
            "schema": { "type": "string" },
            "example": "date_start_iso.asc.nullslast"
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Max rows to return.",
            "schema": { "type": "integer", "minimum": 1, "maximum": 1000, "default": 500 }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "Rows to skip, for pagination alongside `limit`.",
            "schema": { "type": "integer", "minimum": 0, "default": 0 }
          }
        ],
        "responses": {
          "200": {
            "description": "Zero or more event rows.",
            "content": {
              "application/json": {
                "schema": { "type": "array", "items": { "$ref": "#/components/schemas/EventRow" } },
                "example": [
                  {
                    "title": "Beyond Tokens: An AI Developer Meetup",
                    "date_start_iso": "2026-06-12T00:30:00+00:00",
                    "date_end_iso": null,
                    "venue": "The Pearl",
                    "image_url": "https://example.com/cover.jpg",
                    "hosts": ["SF AI Meetup"],
                    "categories_event_website": ["ai"],
                    "summary_ai_enriched": "A developer-focused meetup on building with LLMs.",
                    "categories_summary_ai": ["ai", "meetup"],
                    "summary_keywords_ai": "llm, agents, developer tools",
                    "coordinates": "37.7749,-122.4194",
                    "url_sanitized_resolved": "https://lu.ma/example"
                  }
                ]
              }
            }
          },
          "401": { "$ref": "#/components/responses/MissingApiKey" }
        }
      }
    },
    "/stats_public": {
      "get": {
        "operationId": "getPublicStats",
        "summary": "Landing-page stat strip",
        "description": "A single aggregate row: total published events, upcoming count, and distinct mapped venues. Always call with `select=*&limit=1`.",
        "parameters": [
          {
            "name": "select",
            "in": "query",
            "schema": { "type": "string", "const": "*" },
            "required": true
          },
          {
            "name": "limit",
            "in": "query",
            "schema": { "type": "integer", "const": 1 },
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "A one-element array holding the stats row.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": { "$ref": "#/components/schemas/StatsRow" },
                  "maxItems": 1
                },
                "example": [{ "total_events": 8426, "upcoming_events": 293, "venues_mapped": 1210 }]
              }
            }
          },
          "401": { "$ref": "#/components/responses/MissingApiKey" }
        }
      }
    }
  },
  "components": {
    "responses": {
      "MissingApiKey": {
        "description": "No `apikey` header/param sent, or it was rejected. Live-verified error shape.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "message": { "type": "string" },
                "hint": { "type": ["string", "null"] }
              },
              "required": ["message"]
            },
            "example": {
              "message": "No API key found in request",
              "hint": "No `apikey` request header or url param was found."
            }
          }
        }
      }
    },
    "securitySchemes": {
      "publishableKey": {
        "type": "apiKey",
        "in": "header",
        "name": "apikey",
        "description": "Supabase publishable key. Also send the same value as `Authorization: Bearer <key>`. This key is already public — it ships inside sfclarity's own frontend bundle — and is RLS-gated to published rows only."
      }
    },
    "schemas": {
      "EventRow": {
        "type": "object",
        "description": "One row of events_enriched. Every column except title may be null.",
        "properties": {
          "title": { "type": "string" },
          "date_start_iso": { "type": ["string", "null"], "format": "date-time" },
          "date_end_iso": { "type": ["string", "null"], "format": "date-time" },
          "venue": { "type": ["string", "null"] },
          "image_url": { "type": ["string", "null"], "format": "uri" },
          "hosts": { "type": ["array", "null"], "items": { "type": "string" } },
          "categories_event_website": { "type": ["array", "null"], "items": { "type": "string" } },
          "summary_ai_enriched": { "type": ["string", "null"] },
          "categories_summary_ai": { "type": ["array", "null"], "items": { "type": "string" } },
          "summary_keywords_ai": {
            "type": ["string", "null"],
            "description": "Comma-joined keyword list, not a JSON array."
          },
          "coordinates": {
            "type": ["string", "null"],
            "description": "\"lat,lng\". May be the city fallback centroid when no real geocode was found -- not a precise venue location in that case."
          },
          "url_sanitized_resolved": { "type": ["string", "null"], "format": "uri" }
        },
        "required": ["title"]
      },
      "StatsRow": {
        "type": "object",
        "properties": {
          "total_events": { "type": "integer" },
          "upcoming_events": { "type": "integer" },
          "venues_mapped": { "type": "integer" }
        },
        "required": ["total_events", "upcoming_events", "venues_mapped"]
      }
    }
  }
}
