openapi: 3.1.0
info:
  title: Ripple Ventures public API
  summary: "Portfolio, hiring, and Tank Talks endpoints on rippleventures.com"
  description: "Public, read-only HTTP API for Ripple Ventures (Canadian inception-stage VC). Use it to fetch the portfolio, open roles at portfolio companies, hiring counts, and Tank Talks episodes (JSON + RSS). Official CLI: `npx rippleventures`. Developer index: https://www.rippleventures.com/docs. Navigation for agents: https://www.rippleventures.com/llms.txt. This is not an authenticated product API and does not accept pitches or LP documents."
  version: 1.1.0
  contact:
    name: Ripple Ventures
    email: "jones@rippleventures.com"
    url: "https://www.rippleventures.com"
  license:
    name: Public site data
    identifier: LicenseRef-Public-Site
servers:
  - url: "https://www.rippleventures.com"
    description: Production (www)
tags:
  - name: Portfolio
    description: Firm portfolio as structured JSON
  - name: Hiring
    description: Open roles at portfolio companies
  - name: Content
    description: Published Ripple Ventures media
  - name: Discovery
    description: How agents find and call these operations
paths:
  /portfolio.json:
    get:
      operationId: listPortfolioCompanies
      tags:
        - Portfolio
      summary: List Ripple Ventures portfolio companies
      description: "Returns every portfolio company the firm publishes on the homepage: name, domain, founders, fund, stage, geography, and related fields. Prefer this over scraping HTML."
      responses:
        200:
          description: Portfolio snapshot
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PortfolioSnapshot"
  /api/portfolio-jobs:
    get:
      operationId: getPortfolioCompanyJobs
      tags:
        - Hiring
      summary: List open roles for one portfolio company
      description: Returns current public jobs for a portfolio company identified by slug. Slug is the company name lowercased with non-alphanumerics turned into hyphens (e.g. Blank Bio → blank-bio). Unknown slugs return 404 JSON. Missing company returns 400 JSON.
      parameters:
        - name: company
          in: query
          required: true
          description: Company slug. Derive it from a name in GET /portfolio.json.
          schema:
            type: string
            minLength: 1
            examples:
              - arlo
              - blank-bio
      responses:
        200:
          description: Company plus its open roles
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PortfolioJobsResponse"
        400:
          description: Missing company query parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
        404:
          description: No portfolio company matches the slug
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
        503:
          description: Jobs backend temporarily unavailable
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
  /api/portfolio-hiring-counts:
    get:
      operationId: getPortfolioHiringCounts
      tags:
        - Hiring
      summary: Open-role counts keyed by company slug
      description: Returns a map of company slug → number of public open roles. Use with GET /portfolio.json to attach counts to company records.
      responses:
        200:
          description: Slug-to-count map
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HiringCountsResponse"
        503:
          description: Hiring counts temporarily unavailable
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
  /api/tanktalks-episodes:
    get:
      operationId: listTankTalksEpisodes
      tags:
        - Content
      summary: List Tank Talks episodes for showcase embeds
      description: "JSON showcase feed of Tank Talks episodes: title, episode number, published date, duration, thumbnail, Substack URL, and description. Use this to render episode cards without scraping /tanktalks. Filter with ?n=<episode> or ?limit=<count>. The same show also has an RSS feed at /api/tanktalks-feed."
      parameters:
        - name: n
          in: query
          required: false
          description: "Episode number (itunes:episode), e.g. 334"
          schema:
            type: string
            examples:
              - 334
        - name: limit
          in: query
          required: false
          description: Max episodes to return (1–100). Default is the full snapshot.
          schema:
            type: integer
            minimum: 1
            maximum: 100
      responses:
        200:
          description: Show metadata plus episode cards
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TankTalksEpisodesResponse"
        400:
          description: Empty n or invalid limit
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
        404:
          description: No episode matches n
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
  /api/tanktalks-feed:
    get:
      operationId: getTankTalksFeed
      tags:
        - Content
      summary: Tank Talks podcast RSS feed
      description: Proxies the Tank Talks podcast RSS from Substack. Success is application/rss+xml. Failures are application/json ApiError (502) so agents can parse them.
      responses:
        200:
          description: RSS 2.0 document
          content:
            application/rss+xml:
              schema:
                type: string
                description: RSS XML
        502:
          description: Upstream feed missing or invalid
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
  /openapi.json:
    get:
      operationId: getOpenApiSpec
      tags:
        - Discovery
      summary: This OpenAPI 3.1 document
      description: Machine-readable contract for function calling. Also published as YAML at /api/openapi.yaml.
      responses:
        200:
          description: OpenAPI 3.1 document
          content:
            application/json:
              schema:
                type: object
components:
  schemas:
    ApiError:
      type: object
      additionalProperties: false
      required:
        - error
        - code
        - message
        - hint
      description: "Every JSON error from /api/* uses this shape so agents can branch on `code` without scraping HTML."
      properties:
        error:
          type: string
          description: "Human-readable message (same text as `message`)."
        code:
          type: string
          description: "Stable machine code: MISSING_COMPANY, COMPANY_NOT_FOUND, UPSTREAM_UNAVAILABLE, NOT_FOUND, JOBS_UNAVAILABLE, HIRING_COUNTS_UNAVAILABLE, INVALID_EPISODE, EPISODE_NOT_FOUND, INVALID_LIMIT."
        message:
          type: string
        hint:
          type: string
          description: What to try next (URL or parameter).
      examples:
        - error: No API endpoint at /api/unknown
          code: NOT_FOUND
          message: No API endpoint at /api/unknown
          hint: "See https://www.rippleventures.com/openapi.json for documented operations, or https://www.rippleventures.com/docs for Ripple Ventures developer resources."
    PortfolioCompany:
      type: object
      required:
        - name
      properties:
        name:
          type: string
        domain:
          type:
            - string
            - "null"
        description:
          type:
            - string
            - "null"
        industry:
          type:
            - array
            - "null"
          items:
            type: string
        geography:
          type:
            - array
            - "null"
          items:
            type: string
        fund:
          type:
            - string
            - "null"
        space:
          type:
            - string
            - "null"
        vertical:
          type:
            - string
            - "null"
        stage:
          type:
            - string
            - "null"
        status:
          type:
            - string
            - "null"
        year_invested:
          type:
            - integer
            - "null"
        month_invested:
          type:
            - string
            - "null"
        press_url:
          type:
            - string
            - "null"
        founders:
          type:
            - array
            - "null"
          items:
            type: string
        founder_linkedins:
          type:
            - array
            - "null"
          items:
            type:
              - string
              - "null"
    PortfolioSnapshot:
      type: object
      required:
        - firm
        - site
        - updated_at
        - count
        - companies
      properties:
        firm:
          type: string
          const: Ripple Ventures
        site:
          type: string
          format: uri
        updated_at:
          type: string
          format: date-time
        count:
          type: integer
        companies:
          type: array
          items:
            $ref: "#/components/schemas/PortfolioCompany"
    PortfolioJobCompany:
      type: object
      properties:
        slug:
          type: string
        name:
          type: string
        domain:
          type:
            - string
            - "null"
        jobs_url:
          type:
            - string
            - "null"
        description:
          type:
            - string
            - "null"
        vertical:
          type:
            - string
            - "null"
        industry:
          type:
            - array
            - "null"
          items:
            type: string
        geography:
          type:
            - array
            - "null"
          items:
            type: string
        stage:
          type:
            - string
            - "null"
        open_roles_count:
          type: integer
    PortfolioJob:
      type: object
      properties:
        id:
          type: string
        title:
          type: string
        job_url:
          type: string
        location:
          type: string
        posted_display:
          type:
            - string
            - "null"
        compensation_display:
          type:
            - string
            - "null"
        yoe_display:
          type: string
    PortfolioJobsResponse:
      type: object
      required:
        - company
        - jobs
      properties:
        company:
          $ref: "#/components/schemas/PortfolioJobCompany"
        jobs:
          type: array
          items:
            $ref: "#/components/schemas/PortfolioJob"
    TankTalksShow:
      type: object
      required:
        - name
        - host
        - site
        - rss
      properties:
        name:
          type: string
        host:
          type: string
        firm:
          type: string
        description:
          type: string
        site:
          type: string
          format: uri
        substack:
          type: string
          format: uri
        apple:
          type: string
          format: uri
        youtube:
          type: string
          format: uri
        spotify:
          type: string
          format: uri
        rss:
          type: string
          format: uri
    TankTalksEpisode:
      type: object
      required:
        - n
        - title
        - published_at
        - url
      properties:
        n:
          type: string
          description: Episode number
        title:
          type: string
        published_at:
          type: string
          description: RFC-822 date from the RSS feed
        duration:
          type: string
          examples:
            - 23 min
        thumbnail:
          type:
            - string
            - "null"
          format: uri
          description: Square cover art (Substack CDN)
        url:
          type: string
          format: uri
          description: Episode page on Substack
        description:
          type: string
    TankTalksEpisodesResponse:
      type: object
      required:
        - show
        - count
        - episodes
      properties:
        show:
          $ref: "#/components/schemas/TankTalksShow"
        count:
          type: integer
        episodes:
          type: array
          items:
            $ref: "#/components/schemas/TankTalksEpisode"
    HiringCountsResponse:
      type: object
      required:
        - counts
      properties:
        counts:
          type: object
          additionalProperties:
            type: integer
          description: Map of company slug to open-role count