The perks of a good OpenAPI spec
![Tom MacWright](/_astro/tom.Cx99M2g4_Z1L13eJ.webp)
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
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:
// Import the SDKimport ValTown from "npm:@valtown/sdk";// Initialize itconst valTown = new ValTown();// Use itconsole.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:
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