openapi: "3.2.0"
info:
  title: Hack Club Events API
  version: "1.0.0"
  summary: The official public API for all Hack Club events.
  description: "In the past we used to have issues where we would have many different APIs claiming to be the source of truth for Hack Club events, however each would be somehow outdated or incomplete. This API is now supposed to be the single source of truth. It fetches directly from our internal Airtables (specifically the Unified YSWS table) and is the only API that is guaranteed to be most up to date.\n\nDue to Airtable ratelimits, we cache for 5 minutes maximum. Any image URLs signed by Airtable only last for about 2 hours, so don't hotlink them, but cache them heavily or just store the raw image. These are unlikely to change frequently.\n\nEvery error is JSON with a stable `code`, a `message`, a `hint`, and the HTTP `status` repeated in the body for your convenience. This API and documentation is made with AI agents in mind, so point them here if you want them to understand the API. Pages on this site answer `Accept: text/markdown`; see /llms.txt for the page index.\n\nWhile the site does serve some other API endpoints, these are deliberately undocumented and should not be built on as they only serve internal purposes. The only public API is under `/api/v1/events`.\n\nVersioning and deprecation policy: https://ru.hackclub.com/api/versioning. Breaking changes require a new major URL version. Hack Club normally gives at least 90 days' notice before removing a documented endpoint. No documented v1 endpoint is currently deprecated or scheduled for removal."
  contact:
    name: Hack Club
    email: "echo@hackclub.com"
  x-versioning-policy: "https://ru.hackclub.com/api/versioning"
  license:
    name: MIT
    identifier: MIT
servers:
  - url: "https://ru.hackclub.com"
    description: This deployment
security:
  - {}
externalDocs:
  description: Rendered API reference
  url: "https://ru.hackclub.com/api/v1/docs"
tags:
  - name: events
    description: Hack Club events
paths:
  "/api/v1/events":
    get:
      tags:
        - events
      operationId: listEvents
      summary: List events
      description: "Every Hack Club event, with the styling its card uses on the site."
      parameters:
        - name: status
          in: query
          required: false
          description: Only events in these states. Repeat the parameter or pass a comma-separated list.
          schema:
            type: array
            items:
              type: string
              enum:
                - upcoming
                - ongoing
                - ended
          explode: true
        - name: sort
          in: query
          required: false
          description: Field to sort by.
          schema:
            type: string
            enum:
              - startDate
              - announcedAt
              - name
            default: startDate
          explode: false
        - name: order
          in: query
          required: false
          description: Sort direction.
          schema:
            type: string
            enum:
              - asc
              - desc
            default: desc
          explode: false
        - name: limit
          in: query
          required: false
          description: "Maximum number of events to return, 1–100."
          schema:
            type: integer
            minimum: 1
            maximum: 100
          explode: false
      responses:
        "200":
          description: Events.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/EventList"
        "400":
          description: A query parameter was not understood.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        "500":
          description: The server is missing its Airtable credentials.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        "502":
          description: The upstream Airtable request failed.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/api/v1/events/{idOrSlug}":
    get:
      tags:
        - events
      operationId: getEvent
      summary: Get one event
      description: "Look an event up by its `id` or its `slug`. Prefer the `id`: a slug is derived from the name and changes if the event is renamed for whatever reason"
      parameters:
        - name: idOrSlug
          in: path
          required: true
          description: "An event's Airtable record id, or its slug."
          schema:
            type: string
      responses:
        "200":
          description: The event.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/EventItem"
        "404":
          description: No event has that id or slug.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        "500":
          description: The server is missing its Airtable credentials.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        "502":
          description: The upstream Airtable request failed.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/api/v1/events/rss":
    get:
      tags:
        - events
      operationId: getEventsFeed
      summary: "Newly announced events, as RSS"
      description: "An RSS 2.0 announcement feed: the 20 most recently added events, newest first, whatever state they are in."
      parameters:
        - name: limit
          in: query
          required: false
          description: "Maximum number of events to return, 1–100."
          schema:
            type: integer
            minimum: 1
            maximum: 100
          explode: false
      responses:
        "200":
          description: An RSS 2.0 feed.
          content:
            application/rss+xml:
              schema:
                type: string
        "400":
          description: A query parameter was not understood.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        "500":
          description: The server is missing its Airtable credentials.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        "502":
          description: The upstream Airtable request failed.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
components:
  schemas:
    Error:
      type: object
      description: Structured error body returned by every endpoint under /api.
      required:
        - error
        - code
        - message
        - status
        - documentation_url
      properties:
        error:
          type: string
          description: "Human-readable message. Same text as `message`."
        code:
          type: string
          description: Stable machine-readable identifier for the failure.
          enum:
            - bad_request
            - unauthorized
            - forbidden
            - not_found
            - method_not_allowed
            - not_acceptable
            - unsupported_media_type
            - payload_too_large
            - server_misconfigured
            - internal_error
            - upstream_error
        message:
          type: string
        hint:
          type: string
          description: What the caller can change to make the request succeed.
        status:
          type: integer
          description: "HTTP status code, repeated in the body."
        documentation_url:
          type: string
          format: uri
    EventBackground:
      type: object
      description: "How the event's card is filled. `null` if the event has no swag yet."
      required:
        - type
        - color
        - imageUrl
      properties:
        type:
          type: string
          enum:
            - color
            - image
          description: "Which of `color` and `imageUrl` the card actually uses."
        color:
          type: string
        imageUrl:
          type:
            - string
            - "null"
          description: "Signed Airtable URLs only last for about 2 hours, so don't hotlink them, but cache them heavily: https://support.airtable.com/articles/9671148410-airtable-attachment-url-behavior"
    EventTheme:
      type: object
      description: "The event's card colours. `null` if the event has no swag yet."
      required:
        - text
        - accent
        - logoSize
        - button
      properties:
        text:
          type: string
        accent:
          type: string
        logoSize:
          type: number
        button:
          type: object
          required:
            - color
            - textColor
            - borderRadius
            - borderWidth
            - borderColor
          properties:
            color:
              type: string
            textColor:
              type: string
            borderRadius:
              type: number
            borderWidth:
              type: number
            borderColor:
              type: string
    EventInPerson:
      type: object
      description: "Where and when the event happens in person. `null` if it is online only."
      required:
        - start
        - end
        - location
      properties:
        start:
          type:
            - string
            - "null"
          format: date
        end:
          type:
            - string
            - "null"
          format: date
        location:
          type:
            - string
            - "null"
    Event:
      type: object
      required:
        - id
        - slug
        - name
        - status
        - startDate
        - announcedAt
      properties:
        id:
          type: string
          description: "Airtable record id. This identifier is stable, so store this instead of the slug."
        slug:
          type: string
          description: Derived from the name. This will change if the event is renamed for whatever reason.
        name:
          type: string
        description:
          type:
            - string
            - "null"
        url:
          type:
            - string
            - "null"
          description: "The event's own website."
        status:
          type: string
          enum:
            - upcoming
            - ongoing
            - ended
        startDate:
          type: string
          format: date
        endDate:
          type:
            - string
            - "null"
          format: date
          description: "`null` means the event runs indefinitely."
        announcedAt:
          type: string
          format: date-time
          description: When the event was added.
        format:
          type:
            - string
            - "null"
          enum:
            - in-person
            - online
            - both
            - null
        inPerson:
          oneOf:
            - "$ref": "#/components/schemas/EventInPerson"
            - type: "null"
        projectTypes:
          type: array
          items:
            type: string
            enum:
              - Electronics
              - Websites
              - Software
              - Gamedev
              - CAD
              - Other
        slackChannel:
          type:
            - string
            - "null"
          description: "Channel name, without the leading `#`."
        slackUrl:
          type:
            - string
            - "null"
        requirements:
          type:
            - string
            - "null"
        pinned:
          type: boolean
        logoUrl:
          type:
            - string
            - "null"
          description: "Signed Airtable URLs only last for about 2 hours, so don't hotlink them, but cache them heavily: https://support.airtable.com/articles/9671148410-airtable-attachment-url-behavior"
        background:
          oneOf:
            - "$ref": "#/components/schemas/EventBackground"
            - type: "null"
        theme:
          oneOf:
            - "$ref": "#/components/schemas/EventTheme"
            - type: "null"
    EventList:
      type: object
      required:
        - data
        - meta
      properties:
        data:
          type: array
          items:
            "$ref": "#/components/schemas/Event"
        meta:
          type: object
          required:
            - count
            - generatedAt
          properties:
            count:
              type: integer
              description: "Number of events in `data`, after any filtering and `limit`."
            generatedAt:
              type: string
              format: date-time
    EventItem:
      type: object
      required:
        - data
        - meta
      properties:
        data:
          "$ref": "#/components/schemas/Event"
        meta:
          type: object
          required:
            - generatedAt
          properties:
            generatedAt:
              type: string
              format: date-time
