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.

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.
| operationId | Route | Use |
|---|---|---|
| getApiJobStatus | GET /v1/jobs/{id}/status | Poll loop |
| getApiJobResult | GET /v1/jobs/{id}/result | Fetch output once terminal |
| cancelApiJob | POST /v1/jobs/{id}/cancel | Cancel before generation starts |
| listApiUsage | GET /v1/usage | Ledger 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
- OpenRouter: poll video every 30 s. Sume: obey next_poll_after_seconds
OpenRouter recommends a 30 second poll on video. Sume's jobs API returns next_poll_after_seconds, else backoff. A Python poller that does both.
- OpenRouter's video expired event has no Sume twin: one normalizer
OpenRouter sends completed, failed, cancelled and expired video events; Sume sends three job events. A Python normalizer and verifier for both.
- Pick the cheapest Sume image row for a ratio and 3 refs (Python)
A 24-line Python script reads GET /v1/images/models and the endpoints route, filters by aspect ratio and reference count, and sorts by billed price.
- Pick the highest resolution a Sume video model lists (Python)
Sume's catalog row is now the one resolution list for both video endpoints. A short Python helper reads supported_resolutions and steps down instead of failing.
Written by Sume