Type-safe API usage

This guide explains how to consume the Mindoo API with full type safety using generated types from the OpenAPI specification.

The instructions below are specific to TypeScript. In other programming languages, the same principles apply.

Prerequisites

Before you begin, make sure you have:

  • Completed the authentication flow and obtained a valid id_token
  • A TypeScript project configured

Generating TypeScript types

The Mindoo API provides an OpenAPI specification that can be used to generate TypeScript types automatically.

Install dependencies

Install openapi-typescript and openapi-fetch:

npm i openapi-fetch
npm i -D openapi-typescript typescript
pnpm add openapi-fetch
pnpm add -D openapi-typescript typescript
yarn add openapi-fetch
yarn add -D openapi-typescript typescript
bun add openapi-fetch
bun add -D openapi-typescript typescript
Generate types from the OpenAPI spec

Run the CLI to generate type definitions:

npx openapi-typescript https://api.mindoo.ai/openapi.json -o ./src/lib/mindoo.d.ts
pnpm exec openapi-typescript https://api.mindoo.ai/openapi.json -o ./src/lib/mindoo.d.ts
yarn dlx openapi-typescript https://api.mindoo.ai/openapi.json -o ./src/lib/mindoo.d.ts
bunx openapi-typescript https://api.mindoo.ai/openapi.json -o ./src/lib/mindoo.d.ts

This creates a TypeScript definition file with all API types.

Create a type-safe client

Use openapi-fetch to create a fully typed API client:

import createClient from "openapi-fetch"
import type { paths } from "./mindoo.ts"

const mindooClient = createClient<paths>({
  baseUrl: "https://api.mindoo.ai",
  headers: {
    Authorization: `Bearer ${idToken}`,
  },
})

Making API requests

All requests are fully typed, with autocompletion for paths, parameters, and response types.

Example: creating an agent

const { data, error } = await mindooClient.POST("/agent", {
  body: {
    type: "AI_CONSULTATION",
    name: "Cardiology Pre-Visit",
    specialization: "CARDIOLOGY",
    consultationAgentType: "PRE_VISIT",
  },
})

if (error) {
  console.error("Failed to create agent:", error)
  return
}

// `data` is fully typed as the Agent response
console.log("Created agent:", data.id)

Handling future API changes

The Mindoo API uses a special %future added value pattern for enums and discriminated unions. This ensures your code remains forward-compatible when new values are added to the API.

Why %future added value exists

When writing code that handles the API response, you can use linting rules to make sure that all possible response types are handled. All enumerated properties (“enums” or string literal unions) will contain an additional property %future added value that encourages you to consider what to do when a future version of the API widens the response type, and returns a value that is not explicitly considered by the consuming code.

By properly considering what to do in the default case, your code won’t break when the Mindoo API adds a new enum member (e.g., a new specialization or status), and hence additive changes to the API won’t be considered breaking changes.

For example, the Agent type is a discriminated union:

type Agent = {
  type: "AI_CONSULTATION"
  id: string
  name: string | null
  // ... other fields
} | {
  type: "%future added value"
}

When consuming an Agent-typed response, it is a good idea to handle all possible types of agents:

function handleAgent(agent: Agent) {
  switch (agent.type) {
    case "AI_CONSULTATION":
      console.log("Handling AI consultation agent with id", agent.id)
      break

    default:
      exhaustivenessCheck(agent.type)
      console.log("Unknown agent type, skipping")
      break
  }
}

The exhaustivenessCheck helper function enforces that all known cases are handled:

/**
 * No-op utility function to enforce exhaustive switch statements.
 * TypeScript will error if a known case is not handled above.
 */
export const exhaustivenessCheck = (_: "%future added value") => undefined

Never explicitly handle %future added value as a case. It should only appear in the default branch with exhaustivenessCheck. This ensures TypeScript catches any unhandled known values.

Bad example

switch (agent.type) {
  case "AI_CONSULTATION":
    handleAiConsultationAgent(agent)
    break
  case "%future added value":
    // ❌ Wrong: this case will never match; the Mindoo API will never send an
    // explicit `%future added value` response. Any future unknown values will
    // still fall through.
    handleUnknown()
    break
}

Good example

switch (agent.type) {
  case "AI_CONSULTATION":
    handleAiConsultationAgent(agent)
    break
  default: // ✅ Correct: handles all future-added cases
    exhaustivenessCheck(agent.type) // Ensures that we handled all known types
    handleUnknown()
    break
}