openapi: 3.1.0
info:
  title: VibeInfra Public API
  version: "1.0.0"
  summary: Course catalog, live platform capacity and learner progress for the VibeInfra SRE flight simulator.
  description: |
    VibeInfra is a learn-by-doing infrastructure engineering platform. Engineers troubleshoot
    realistic production incidents (Linux kernel, Docker, Kubernetes, Nginx, PostgreSQL, Kafka,
    AWS) inside live disposable multi-container sandboxes with automated grading.

    This document describes the HTTP surface at `https://api.vibeinfra.id`. Everything under
    "Catalog", "Platform" and "Growth" is unauthenticated and safe for agents and crawlers to
    read — the course catalog is the platform's public index and is intentionally open.
    Endpoints marked with the `sessionCookie` security scheme act on one learner's own account
    and require a signed-in session.

    Machine-readable siblings of this document:
      - ARD manifest: https://vibeinfra.id/.well-known/ard.json
      - MCP server card: https://vibeinfra.id/.well-known/mcp.json
      - LLM site summary: https://vibeinfra.id/llms.txt
  contact:
    name: VibeInfra Developer Relations
    url: https://vibeinfra.id/developers/
    email: hello@vibeinfra.id
  license:
    name: Documentation licensed CC BY 4.0
    url: https://creativecommons.org/licenses/by/4.0/
externalDocs:
  description: VibeInfra developer portal
  url: https://vibeinfra.id/developers/
servers:
  - url: https://api.vibeinfra.id/api/v1
    description: Production
  - url: http://localhost:8080/api/v1
    description: Local development backend
security:
  - sessionCookie: []
  - bearerAuth: []
tags:
  - name: Catalog
    description: The public course and incident catalog. No authentication.
  - name: Platform
    description: Health, live sandbox capacity and public platform telemetry. No authentication.
  - name: Growth
    description: Waitlist capture and the 2AM Live event funnel. No authentication.
  - name: Labs
    description: Starting, grading and tearing down an incident sandbox.
  - name: Account
    description: The signed-in learner's own session, profile and completions.
paths:
  /health:
    get:
      operationId: getHealth
      tags: [Platform]
      summary: Liveness probe
      description: Returns `ok` while the API engine is serving. Used by the load balancer.
      security: []
      responses:
        "200":
          description: The API is operational.
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    examples: ["ok"]
                  message:
                    type: string
                    examples: ["VibeInfra Labs API Engine Operational"]
  /courses:
    get:
      operationId: listCourses
      tags: [Catalog]
      summary: List every course and incident in the catalog
      description: |
        Returns the full catalog as a flat array. This is the canonical machine-readable index
        of what VibeInfra teaches; agents building a learning path should start here.
      security: []
      parameters:
        - name: locale
          in: query
          required: false
          description: BCP 47 language tag. Courses without a translation fall back to English.
          schema:
            type: string
            examples: ["id"]
      responses:
        "200":
          description: The catalog.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/CourseSummary"
        "405":
          $ref: "#/components/responses/MethodNotAllowed"
  /courses/{courseId}:
    get:
      operationId: getCourse
      tags: [Catalog]
      summary: Fetch one course blueprint
      description: |
        Returns the full blueprint for a single course, including its chapters, objectives and
        tech stack. `courseId` values come from `GET /courses`.
      security: []
      parameters:
        - name: courseId
          in: path
          required: true
          description: Catalog identifier, e.g. `incident-22-k8s-crashloop`.
          schema:
            type: string
            examples: ["incident-22-k8s-crashloop"]
        - name: locale
          in: query
          required: false
          schema:
            type: string
      responses:
        "200":
          description: The course blueprint.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Course"
        "400":
          description: No course ID was supplied.
        "404":
          $ref: "#/components/responses/NotFound"
  /glossary:
    get:
      operationId: getGlossary
      tags: [Catalog]
      summary: Infrastructure glossary
      description: Localised definitions of the infrastructure terms used across the catalog.
      security: []
      parameters:
        - name: lang
          in: query
          required: false
          description: Language code; unknown codes fall back to the authored English text.
          schema:
            type: string
            examples: ["id"]
      responses:
        "200":
          description: Glossary entries.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/GlossaryEntry"
        "405":
          $ref: "#/components/responses/MethodNotAllowed"
        "500":
          description: The glossary source could not be loaded.
  /settings/public:
    get:
      operationId: getPublicSettings
      tags: [Platform]
      summary: Public feature flags
      description: |
        Non-sensitive runtime flags the web client reads at boot, plus the live early-access
        seat counters.
      security: []
      responses:
        "200":
          description: Feature flags and seat counters.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicSettings"
  /labs/capacity:
    get:
      operationId: getCapacity
      tags: [Platform]
      summary: Live sandbox capacity
      description: |
        A snapshot of how much sandbox capacity is in use right now, and how many learners are
        waiting. Poll this before calling `POST /labs/start` to predict whether a request will
        be queued.
      security: []
      responses:
        "200":
          description: Capacity snapshot.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Capacity"
  /telemetry/public:
    get:
      operationId: getPublicTelemetry
      tags: [Platform]
      summary: Public platform counters
      security: []
      responses:
        "200":
          description: Aggregate, non-personal platform counters.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicTelemetry"
  /labs/leaderboard:
    get:
      operationId: getLeaderboard
      tags: [Platform]
      summary: XP leaderboard and hall of fame
      security: []
      responses:
        "200":
          description: Ranked learners.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LeaderboardResponse"
  /credits/packs:
    get:
      operationId: listCreditPacks
      tags: [Catalog]
      summary: List purchasable VibeCredits packs
      description: Pack pricing is in Indonesian rupiah (IDR), the platform's billing currency.
      security: []
      responses:
        "200":
          description: Available packs.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/CreditPack"
  /waitlist:
    post:
      operationId: joinWaitlist
      tags: [Growth]
      summary: Join the early-access waitlist
      description: Rate limited to 5 requests per minute per IP.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WaitlistRequest"
      responses:
        "200":
          description: Registered, with the assigned member number.
        "429":
          $ref: "#/components/responses/RateLimited"
  /events/2am-live/status:
    get:
      operationId: get2amLiveStatus
      tags: [Growth]
      summary: 2AM Live event status
      description: |
        Whether the timed 2AM Live outage simulation is open, when it starts, and how many
        seats remain. Authentication is optional; signing in adds this caller's registration
        state to the response.
      security: []
      responses:
        "200":
          description: Event status.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Event2AMStatus"
  /labs/start:
    post:
      operationId: startLab
      tags: [Labs]
      summary: Provision an incident sandbox
      description: |
        Provisions the multi-container environment for one incident and returns the session
        handle used by the terminal WebSocket and the grader.

        Authentication is optional: anonymous callers get the free teaser sandbox, signed-in
        learners get the full catalog subject to their tier and credit balance. When the
        platform is at capacity the response reports a queued status instead of a session —
        check `GET /labs/capacity` first. Rate limited to 5 starts per minute per caller.
      security:
        - {}
        - sessionCookie: []
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [course_id]
              properties:
                course_id:
                  type: string
                  examples: ["incident-22-k8s-crashloop"]
      responses:
        "200":
          description: |
            A provisioned session, or a `queued`/`waitlisted` status when capacity or the
            early-access gate defers the request.
        "405":
          $ref: "#/components/responses/MethodNotAllowed"
        "429":
          $ref: "#/components/responses/RateLimited"
  /labs/check:
    post:
      operationId: checkLab
      tags: [Labs]
      summary: Grade the current sandbox state
      description: |
        Runs the incident's automated evaluator against the live sandbox and awards XP on a
        pass. Rate limited to 12 checks per minute per learner.
      responses:
        "200":
          description: Grading verdict.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"
  /labs/destroy:
    post:
      operationId: destroyLab
      tags: [Labs]
      summary: Tear down the caller's sandbox
      description: Releases the containers immediately rather than waiting for the idle reaper.
      responses:
        "200":
          description: Torn down.
        "401":
          $ref: "#/components/responses/Unauthorized"
  /labs/aar:
    get:
      operationId: getAfterActionReport
      tags: [Labs]
      summary: After-action report for a finished session
      responses:
        "200":
          description: The report.
        "401":
          $ref: "#/components/responses/Unauthorized"
  /auth/me:
    get:
      operationId: getSession
      tags: [Account]
      summary: Describe the current session
      description: |
        Always returns 200. `authenticated` is `false` for anonymous or expired callers rather
        than raising an error, so this is safe to call unconditionally on page load.
      security:
        - {}
        - sessionCookie: []
        - bearerAuth: []
      responses:
        "200":
          description: Session state.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionResponse"
  /auth/logout:
    get:
      operationId: logout
      tags: [Account]
      summary: Clear the session cookie
      responses:
        "200":
          description: Signed out.
  /user/profile:
    get:
      operationId: getUserProfile
      tags: [Account]
      summary: The signed-in learner's profile
      responses:
        "200":
          description: Profile.
        "401":
          $ref: "#/components/responses/Unauthorized"
  /user/completions:
    get:
      operationId: listUserCompletions
      tags: [Account]
      summary: Incidents this learner has completed
      responses:
        "200":
          description: Completions, newest first.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Completion"
        "401":
          $ref: "#/components/responses/Unauthorized"
components:
  securitySchemes:
    sessionCookie:
      type: apiKey
      in: cookie
      name: vibe_jwt
      description: |
        HttpOnly session cookie minted by the GitHub or Google OAuth callback
        (`/api/v1/auth/github`, `/api/v1/auth/google`). Browser clients should send
        `credentials: "include"`.
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        The same session JWT presented as `Authorization: Bearer <token>`, for non-browser
        clients that cannot hold a cookie jar.
  responses:
    Unauthorized:
      description: No valid session was presented.
    NotFound:
      description: No such resource.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    MethodNotAllowed:
      description: This path only serves the documented method.
    RateLimited:
      description: The caller exceeded this endpoint's rate limit.
  schemas:
    CourseSummary:
      type: object
      description: The abbreviated course shape returned by the catalog list.
      required: [id, title, type, category, difficulty, duration_minutes, xp_reward, description, tech_stack, status]
      properties:
        id:
          type: string
          examples: ["incident-22-k8s-crashloop"]
        title:
          type: string
          examples: ["Kubernetes Microservices CrashLoopBackOff"]
        type:
          type: string
          description: '`lab` for an incident simulation, `course` for a guided curriculum module.'
          examples: ["lab"]
        category:
          type: string
          examples: ["Kubernetes"]
        difficulty:
          type: string
          enum: [Beginner, Intermediate, Advanced, Expert]
        domain:
          type: string
          description: Skill-ladder domain this incident belongs to.
        rung:
          type: integer
          description: Position on the SRE skill progression ladder.
        duration_minutes:
          type: integer
        xp_reward:
          type: integer
        description:
          type: string
        tech_stack:
          type: array
          items:
            type: string
          examples: [["Kubernetes", "Ingress", "kubectl"]]
        status:
          type: string
          enum: [enabled, disabled, coming_soon]
        disabled_reason:
          type: string
        admin_only:
          type: boolean
        founding_preview:
          type: boolean
        access_tier:
          type: string
        credit_cost:
          type: integer
          description: VibeCredits charged to start this lab. `0` when the lab is free.
        free_until:
          type: [string, "null"]
          format: date-time
        is_free:
          type: boolean
    Course:
      allOf:
        - $ref: "#/components/schemas/CourseSummary"
        - type: object
          description: The full blueprint, adding the authored teaching content.
          properties:
            objectives:
              type: array
              items:
                type: object
                properties:
                  title:
                    type: string
                  desc:
                    type: string
            chapters:
              type: array
              description: Present on step-graded curriculum courses.
              items:
                type: object
    GlossaryEntry:
      type: object
      required: [id, label, definition]
      properties:
        id:
          type: string
        label:
          type: string
        definition:
          type: string
    PublicSettings:
      type: object
      properties:
        pricing_section_enabled:
          type: boolean
        early_access_gate:
          type: boolean
        early_access_seat_cap:
          type: integer
        early_access_seats_used:
          type: integer
        founding_engineer_cap:
          type: integer
        founding_engineer_used:
          type: integer
        event_2am_enabled:
          type: boolean
    Capacity:
      type: object
      properties:
        total:
          type: integer
          description: Sandbox sessions currently running.
        mode2:
          type: integer
          description: Running full multi-container (Mode 2) sessions.
        mode2_cpu:
          type: number
          description: CPU currently committed to Mode 2 sessions.
        free_lab_cpu:
          type: number
          description: CPU currently committed to free teaser labs.
        queue_length:
          type: integer
          description: Learners waiting for a slot.
    PublicTelemetry:
      type: object
      properties:
        early_access_members:
          type: integer
        unique_visitors:
          type: integer
        active_labs:
          type: integer
        uptime_status:
          type: string
    LeaderboardEntry:
      type: object
      properties:
        rank:
          type: integer
        user_id:
          type: string
        username:
          type: string
        score_xp:
          type: integer
        labs_completed:
          type: integer
        avatar_url:
          type: string
        updated_at:
          type: string
          format: date-time
    LeaderboardResponse:
      type: object
      properties:
        status:
          type: string
        total:
          type: integer
        leaderboard:
          type: array
          items:
            $ref: "#/components/schemas/LeaderboardEntry"
        hall_of_fame:
          type: array
          items:
            $ref: "#/components/schemas/LeaderboardEntry"
    CreditPack:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        price_idr:
          type: integer
        base_credits:
          type: integer
        bonus_credits:
          type: integer
        effective_total:
          type: integer
    WaitlistRequest:
      type: object
      required: [email]
      properties:
        email:
          type: string
          format: email
        name:
          type: string
        github_username:
          type: string
        role:
          type: string
        experience:
          type: string
        company:
          type: string
        interests:
          type: array
          items:
            type: string
        requested_lab:
          type: string
    Event2AMStatus:
      type: object
      properties:
        registered:
          type: boolean
        starts_at:
          type: string
          format: date-time
        seats_remaining:
          type: integer
        enabled:
          type: boolean
    Completion:
      type: object
      properties:
        course_id:
          type: string
        title:
          type: string
        completed_at:
          type: string
          format: date-time
        xp_awarded:
          type: integer
        duration_seconds:
          type: integer
    SessionResponse:
      type: object
      required: [authenticated]
      properties:
        authenticated:
          type: boolean
        user_id:
          type: string
        is_admin:
          type: boolean
        credit_balance:
          type: integer
        subscription_status:
          type: string
        user:
          type: object
          properties:
            id:
              type: string
            username:
              type: string
            name:
              type: string
            email:
              type: string
            avatar_url:
              type: string
    Error:
      type: object
      properties:
        error:
          type: string
