openapi-fetch with Sume's OpenAPI JSON: a typed client in 20 lines

Generate types from Sume's reference JSON with openapi-typescript, then call it with openapi-fetch and an x-api-key middleware. A type-checked sample.

4 min readSume
All posts

Run npx openapi-typescript https://api.sume.com/reference/json -o sume.d.ts, then pass the generated paths type to createClient from openapi-fetch and register a middleware that sets x-api-key. The openapi-fetch docs (read 2026-10-10) describe createClient<paths> and a result of { data, error, response }, so each call is checked against the real route and parameter names.

I generated the types from the repo's current OpenAPI file with openapi-typescript 7.13.0 and type-checked the sample below under Deno with openapi-fetch 0.17.0, so the import and the path string are not guesses.

The client

The auth step follows the middleware pattern from the same docs: client.use takes an object with onRequest, which receives the request and returns it. Sume accepts one credential header, either Bearer or x-api-key, and returns 401 when you send both, so set exactly one in the middleware and send no default Authorization header elsewhere.

import createClient, { type Middleware } from "openapi-fetch";
import type { paths } from "./sume"; // npx openapi-typescript https://api.sume.com/reference/json -o sume.d.ts

const auth: Middleware = {
  onRequest({ request }) {
    request.headers.set("x-api-key", process.env.SUME_API_KEY!);
    return request;
  },
};
const client = createClient<paths>({ baseUrl: "https://api.sume.com" });
client.use(auth);

const { data, error } = await client.GET("/v1/jobs/{id}/status", {
  params: { path: { id: "job_123" } },
});
if (error) throw new Error(JSON.stringify(error));
console.log(data.data.sume_status, data.data.terminal, data.data.next_poll_after_seconds);

What the types catch

The path string /v1/jobs/{id}/status is a key of paths, so a typo is a compile error, and params.path.id is required by type. The status body also types sume_status, terminal and next_poll_after_seconds, which are exactly the three fields a poll loop reads.

The OpenAPI file is version 3.0.3, which uses nullable: true. The generator turns that into | null in the types, so a nullable hint stays a nullable number in your code and forces the fallback branch.

Operations used in typed calls (OpenAPI read 2026-10-10)
operationIdRouteUse
getApiJobStatusGET /v1/jobs/{id}/statusPoll loop
getApiJobResultGET /v1/jobs/{id}/resultFetch output once terminal
cancelApiJobPOST /v1/jobs/{id}/cancelCancel before generation starts
listApiUsageGET /v1/usageLedger reads

Limits of generated types

A type is a promise about the shape, not about the runtime. The generated client does not retry, does not add an Idempotency-Key, and does not wait for a job; you add those. For the submit, put the key in the headers option of that single call and generate it once per intent.

Re-run the generator when the reference file changes, and commit the output so a diff shows exactly which fields moved. That diff is the cheapest API change notice you will get.

If you prefer a generated client with its own request layer, compare Hey API's openapi-ts approach on the same file.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume