B Buffy Agent
Buffy Agent Blog · Engineering

TypeScript types from the Buffy OpenAPI spec (codegen walkthrough)

Generate typed models from the production OpenAPI YAML at api.buffyai.org, then call POST /v1/message and activities routes from Node with less drift and fewer silent JSON mistakes.

TypeScript types from the Buffy OpenAPI spec (codegen walkthrough)

Hand-written fetch calls drift from reality quickly—especially across POST /v1/message, activities CRUD, and webhook-shaped payloads. Buffy publishes a first-class OpenAPI 3 description; this post shows how to turn that YAML into TypeScript types (and optionally a generated SDK) so your integration breaks at compile time when the contract changes.

Start with the map, not a wall of paths: Buffy API reference overview. The integration story lives in Buffy API — integrate your app.

Prerequisites

  • Node 18+ and a package manager (npm, pnpm, or yarn).
  • A server-side Buffy API key from Account → API keys on buffyai.org (never commit it; load from env).
  • Canonical spec URL: https://api.buffyai.org/openapi.yaml (same host as https://api.buffyai.org; interactive docs are often at /swagger).

Fetch the spec (local file or CI)

Download a snapshot for reproducible builds:

curl -fsSL "https://api.buffyai.org/openapi.yaml" -o openapi/buffy.yaml

In CI, you can fail the job if the spec changes unexpectedly:

curl -fsSL "https://api.buffyai.org/openapi.yaml" | shasum -a 256

Store the hash in a comment or artifact log; when Buffy ships breaking changes, regenerate types deliberately and fix compile errors rather than shipping silent runtime bugs.

Option A — Types only with openapi-typescript

openapi-typescript turns the YAML into a paths / components type map—small output, no runtime SDK.

Install (dev dependency):

npm install -D openapi-typescript

Generate (pin the CLI version in package.json; upgrade when you want new OpenAPI features):

npx openapi-typescript https://api.buffyai.org/openapi.yaml -o ./src/generated/buffy-api.ts

Or from the downloaded file:

npx openapi-typescript ./openapi/buffy.yaml -o ./src/generated/buffy-api.ts

Use the types with fetch. Exact paths[...] keys follow whatever the spec names (for example /v1/message). After generation, explore buffy-api.ts in your editor—TypeScript will guide you to the correct requestBody and response shapes.

import type { paths } from "./generated/buffy-api";

const BASE = "https://api.buffyai.org";

type MessageRequest =
  paths["/v1/message"]["post"]["requestBody"]["content"]["application/json"];
type MessageResponse =
  paths["/v1/message"]["post"]["responses"][200]["content"]["application/json"];

export async function sendMessage(
  apiKey: string,
  body: MessageRequest
): Promise<MessageResponse> {
  const res = await fetch(`${BASE}/v1/message`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  if (!res.ok) {
    const text = await res.text();
    throw new Error(`Buffy API ${res.status}: ${text}`);
  }
  return (await res.json()) as MessageResponse;
}

// Example body shape from production OpenAPI — `user_id` + `platform` + `message` are required:
// sendMessage(apiKey, { user_id: "usr_…", platform: "cli", message: "Ping from codegen smoke test." })

First sanity check with curl (fill real user_id from /v1/auth/me or your integration):

export BUFFY_API_KEY="your_key"
curl -sS -X POST "https://api.buffyai.org/v1/message" \
  -H "Authorization: Bearer $BUFFY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"user_id":"usr_your_id","platform":"cli","message":"Ping from curl."}'

If your generated paths keys differ slightly (for example versioned prefixes), adjust the indexed access once—the generated file is the source of truth, not this blog snippet.

Optional — openapi-fetch

If you want a thin typed client instead of raw fetch, pair openapi-typescript with openapi-fetch. The pattern is: generate types, create a client with createClient<paths>({ baseUrl: BASE }), then call typed .POST("/v1/message", { body: ... }). Treat it as syntactic sugar over the same Bearer header rules.

Option B — Generated SDK with OpenAPI Generator

When you want classes or modules emitted for every operation, use OpenAPI Generator with the typescript-fetch generator (names and folders vary by generator version).

One-shot via Docker (avoids local Java installs):

docker run --rm -v "${PWD}:/local" openapitools/openapi-generator-cli generate \
  -i https://api.buffyai.org/openapi.yaml \
  -g typescript-fetch \
  -o /local/generated/buffy-sdk

Trade-offs:

  • Pros: Faster time-to-hello-world for large specs; lots of operations wrapped.
  • Cons: Large diffs on regeneration; you may need to wrap awkward method names; merge conflicts when upgrading.

For many Buffy integrations, Option A + a few hand-written helpers stays easier to own than a full generated SDK.

Auth and safety

  • Send Authorization: Bearer <API_KEY> on every request (see API reference overview).
  • Keep keys in process.env or a secrets manager; block .env from git.
  • Prefer server-side calls. If a mobile or web app needs Buffy, proxy through your backend.

Keep types fresh

Add scripts to package.json:

{
  "scripts": {
    "gen:buffy": "openapi-typescript https://api.buffyai.org/openapi.yaml -o src/generated/buffy-api.ts"
  }
}

Run gen:buffy when you upgrade integrations or when Buffy announces API changes. Fix TypeScript errors locally before deploy—that is the point of codegen.

Next step

Wire the behavior loop: First week with the Buffy API, then Buffy webhook integrations for outbound events. For tool-style access, see MCP tools with the Buffy API.

Further reading