OpenAPI & client generation
Fetch a generated API's OpenAPI document (/openapi.json or /openapi-enhanced.json) and generate typed clients with openapi-typescript, openapi-generator and NSwag.
Every generated API serves an OpenAPI 3.0 document built from your live schema and endpoint configuration:
| URL | Contents |
|---|---|
/openapi.json |
Every route the API serves, with a stable operationId (listArtist, getArtist, createArtist, updateArtist, patchArtist, deleteArtist, …), its parameters (limit, offset, sort, fields, count and one filter per column), request and response schemas, every error status with the {"error": "..."} body, and its authentication |
/openapi-enhanced.json |
The same, plus your documentation settings: title, description, contact, example values and authored examples |
Both work for code generation; the operationIds are the same in both. Disabled endpoints are not in the documents.
Send your API key when fetching the document. Once your API's documentation is published, the
documents are public; until then they need an X-API-Key the API itself would accept: a key
for this API that is not revoked or expired, with API keys enabled for your organization and
the key's IP allowlist (if any) satisfied. Without a key the answer is
401 {"error":"This documentation is private. Provide an X-API-Key header."}; with any other
key, 401 {"error":"This documentation is private. The X-API-Key is not valid for this API."}.
curl -s "$RESTWARD_API_URL/openapi-enhanced.json" \
-H "X-API-Key: $RESTWARD_API_KEY" -o openapi.jsonconst spec = await (await fetch(`${process.env.RESTWARD_API_URL}/openapi-enhanced.json`, {
headers: { "X-API-Key": process.env.RESTWARD_API_KEY },
})).json();spec = session.get(f"{api}/openapi-enhanced.json").json()var spec = await http.GetStringAsync("/openapi-enhanced.json");
await File.WriteAllTextAsync("openapi.json", spec);The commands below were run against the document of the Chinook sample API. The generated C# client was compiled.
TypeScript: openapi-typescript
Generates types only; pair it with openapi-fetch
for a typed fetch wrapper.
npx openapi-typescript openapi.json -o src/chinook.d.ts
npm install openapi-fetchimport createClient from "openapi-fetch";
import type { paths } from "./chinook";
const client = createClient<paths>({
baseUrl: process.env.RESTWARD_API_URL,
headers: { "X-API-Key": process.env.RESTWARD_API_KEY },
});
const { data, error } = await client.GET("/api/public/artist", { params: { query: { limit: 3, name: "AC/DC" } } });Python and other languages: openapi-generator
openapi-generator supports dozens of languages. With Docker:
docker run --rm -v "$PWD:/local" openapitools/openapi-generator-cli generate \
-i /local/openapi.json -g python -o /local/chinook-client --package-name chinook_client
pip install ./chinook-clientimport os
import chinook_client
config = chinook_client.Configuration(host=os.environ["RESTWARD_API_URL"])
config.api_key["ApiKeyAuth"] = os.environ["RESTWARD_API_KEY"]
with chinook_client.ApiClient(config) as client:
api = chinook_client.DefaultApi(client)
artists = api.list_artist(limit=3)Swap -g python for typescript-fetch, java, go, kotlin, … as needed.
C#: NSwag
dotnet tool install -g NSwag.ConsoleCore
nswag openapi2csclient /input:openapi.json /output:ChinookClient.cs /namespace:Chinook \
/classname:ChinookClient /jsonLibrary:SystemTextJson /generateOptionalParameters:trueNSwag's default settings produce a client that compiles. /generateOptionalParameters:true
makes the query parameters optional, so you pass only the ones you need.
var http = new HttpClient();
http.DefaultRequestHeaders.Add("X-API-Key", Environment.GetEnvironmentVariable("RESTWARD_API_KEY"));
var client = new Chinook.ChinookClient(Environment.GetEnvironmentVariable("RESTWARD_API_URL"), http);
var artists = await client.ListArtistAsync(limit: 3);Things to know about the generated clients
- The base URL is the platform address. The first
serversentry is the address RestWard assigned to your API (the one on your dashboard and inrestward list); generated clients use it by default. Your documentation custom domain is listed after it, never first, once one of your organization's verified domains covers it: verification proves you own the domain, not that it routes to your API. Neither ever comes from the request or from text in your settings. When the platform has no address for the API, the document has noservers: pass the base URL to the client, as in the examples above. - Equality filters are typed parameters; operators are not. List methods take
limit,offset,sort,fields,countand one parameter per column (name=AC/DC). The operator formcolumn[op]=value(filters) cannot be described as a typed parameter: add those query parameters yourself or call the list route with a plain HTTP client. count=truechanges the response shape. The list response is documented as an array of rows; withcount=truethe body is{"total": n, "items": [...]}(the<table>_pageschema) and typed list methods cannot read it. Call the route directly when you need the total, or read theX-Total-Countheader.fieldsreturns partial rows. No row property isrequired, so models deserialize a projection; the columns you did not ask for are unset.- Create returns the new key. The
201response is the<table>_createdschema,{"id": <new key>}, typed like the first primary-key column.idis nullable where the API cannot return the key: tables without a primary key, and MySQL tables whose key is notAUTO_INCREMENT(MySQL cannot report a key that a column default or a trigger filled in, soidis null when the request omits it). - Generated keys are
readOnly. Identity / auto-increment keys and generated columns are markedreadOnlyand are not in the create and update schemas; the database fills them in. - Prefer
PATCHfor partial updates.PATCH(patch<Table>) updates exactly the columns in the body.PUT(update<Table>) also writes every column in the body, so with a generator that serializes unset properties asnull, pass a complete row. - Errors are typed. Every operation lists the statuses it can return (
400,401,403,404,409,413,422,429,500) with the sharedErrorschema ({"error": "...", "constraint"?, "column"?, "parameter"?});429carriesRetry-After. See Errors & rate limits. - Authentication follows the endpoint configuration. Each operation lists the scheme it
accepts:
ApiKeyAuth(X-API-Key),BearerAuth(JWT) or none for public endpoints. OpenAPI 3.0 cannot describe client certificates, so mTLS endpoints are markedx-restward-auth: mtls; configure the certificate on your HTTP client. Configure one credential per client: a request is checked with the first it carries (API key, then bearer token, then certificate), so a stale API key next to a valid token is a401.