The perks of a good OpenAPI spec

Tom MacWright on

We recently switched from express to fastify for Val Town’s REST API. Fastify gives us a way to strictly define our routes, with all of the path and query string parameters, request and response bodies aligning to a specific schema. This is a big win for robustness, gives us great type-safety with TypeScript, and lets us generate an OpenAPI Specification directly from our server code.

But typesafety and robustness don’t bring home the bacon. What do you, the user, get from our time spent refactoring? It turns out, quite a lot! Our OpenAPI spec let us improve our REST API documentation, generate a new TypeScript SDK, and laid the foundation for letting LLMs use our APIs.

Nice new REST API documentation

Val Town REST API Documentation

First thing, we get way better REST API documentation. It has example requests, with everything from cURL to Python clients. It describes the precise shapes of the objects you’ll get back. You can even test APIs right in the API docs. Hats off to Scalar, whose open source package is powering this.

The docs are based on our OpenAPI spec, which is emitted from Fastify and pulled from our server’s routes. From there on, setting up nice documentation is basically as simple as this:

<script id="api-reference" data-url="./openapi.documented.json"></script>
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>

A TypeScript SDK

Meet our new TypeScript SDK, @valtown/sdk, which we autogenerate from our OpenAPI spec using Stainless:

example.ts
// Import the SDK
import ValTown from "npm:@valtown/sdk";
// Initialize it
const valTown = new ValTown();
// Use it
console.log(await valTown.me.profile.retrieve());

The SDK covers all of our major API methods, and has robust TypeScript types for everything. Methods in our API that are paginated, like listing comments, vals, or search results, are implemented as async iterators in the SDK so you don’t even have to implement any pagination logic.

For Node.js, you can install it as an NPM package:

Terminal window
npm install @valtown/sdk

The SDK works in Deno, of course, but also in Node.js, Bun, and other major runtimes. We’re generating it with tools from our friends at Stainless, and their system keeps it updated so that every time we update the REST API, we can quickly roll a new release of the SDK.

Our AI, Townie, can now call our REST API

Townie is Val Town’s bot - it started as an experiment to see if we could coax an LLM into writing code that fit Val Town’s runtime better. It has a long way to go before it reaches sentience, fortunately/unfortunately, but it can already write some pretty decent code.

But it couldn’t really interact with Val Town until now. Townie just got the ability to call our API directly, which means that it can do things like creating vals, searching for existing code, updating readmes – anything that the API can do, Townie can do too. To be safe, Townie will run GET requests against the API freely, but asks for permission before running POST, PUT, or DELETE requests.

Townie knows the whole layout of the API because OpenAPI 3.1 is compatible with JSON Schema, and the OpenAI Function calling system is based on JSON Schema-defined parameters. So we can smoothly translate from our OpenAPI specification for our REST API, into a definition of functions that OpenAI can call – along with LLM-friendly descriptions for every function, parameter, and return value – and voila, the robot can use the machine. Avoiding OpenAPI-OpenAI mixup typos was one of the more mentally challenging parts of implementing this.

Unfortunately ChatGPT does hallucinate incorrect parameters occasionally, despite having a very precise definition of what is correct. It does this so often that we added a prompt reminding it that it likes to make a particular mistake, but it shouldn’t make that mistake:

When calling the valsCreate function, make sure to follow the schema, which requires that ‘code’ and ‘name’ properties be nested in a ‘body’ object.

This new era of software is funny and awe-inspiring!

Edit this page