Generate a client in any language

Pithy ships TypeScript clients for seven capabilities — auth, organization, payments, consent, multiplayer, matchmaking and leaderboard — and no client in any other language. By default each one is for a web app the Worker itself serves, on its own origin. auth, organization and payments also take an absolute origin and a bearer transport, which is how the scaffolded React Native app calls them. Nothing else needs one. Every route your Worker serves is described in an OpenAPI document, and every language has a generator that turns one into a client. A Swift, Kotlin, C#, Dart or Go app generates its calls rather than writing each one by hand.

Write the document

From the project root:

pithy contract

One document per Worker, at apps/<worker>/openapi.json, and an asyncapi.json beside it for a Worker that serves a socket. Each is read off the app the Worker composes, so it holds exactly the routes that Worker serves: the kit’s capabilities and your own. pithy contract has every flag.

Generate from prod, the default. The route table differs per environment: @pithy-sh/auth mounts its dev sign-in in dev and nowhere else. A shipped client calls production, so that is the table to generate from.

Want to look before you have a project? The kit publishes the same document for every route every capability ships, as docs/openapi.generated.json in the repository. A Worker’s own document is the one to generate from, because it holds only what that Worker composes, plus your routes.

Generate the client

With the OpenAPI Generator, for example:

openapi-generator-cli generate -i apps/api/openapi.json -g swift5 -o ios/PithyClient
openapi-generator-cli generate -i apps/api/openapi.json -g kotlin -o android/pithy-client

For Unity, use the csharp generator’s unityWebRequest library at a pinned version. Unity has the exact command.

Any generator that reads OpenAPI 3.1 works. Each operation has an operationId and a tag naming the capability that mounts it, so the generated methods group the way the capabilities do.

Set the base URL yourself. The document names no server, because your Worker answers at a different origin per environment. Configure the generated client with the one address it calls.

What the document tells a client

Every field’s description. The Zod schemas the Worker validates with are described field by field, and the descriptions travel into the generated code, so your Swift client carries the same documentation as the TypeScript one.

How each operation is authenticated. Three security schemes are declared once: bearer, a token in Authorization: Bearer; session, the browser cookie; and controlPlane, a management client’s scoped credential. Each operation lists the ones it accepts, and carries x-pithy-verification with the same answer in Pithy’s own words. A native app uses bearer, always, and the token is the session token a sign-in answers with, not the accessToken JWT rotation returns beside it. Choosing a client credential is why.

Which scope an admin call needs. An operation that needs more than a verified caller carries x-pithy-scopes.

One error shape for everything. Every operation can answer with the same { error: { code, status, message } } body, described once and referenced everywhere. Branch on code, never on message: the code is stable, and the message is English permanently. Error codes lists every one.

Which of your routes are not finished. A route of yours with no describeRoute is still in the document, written from its validators alone and marked x-pithy-undescribed: true. A generated client reaches it, but it will not know what comes back. pithy contract names each one so you can describe it.

The sockets are a second document

OpenAPI stops at the upgrade. A Worker that composes multiplayer or matchmaking serves a socket, and pithy contract describes it in asyncapi.json: every frame each side sends, the subprotocol to offer, and each game’s own action and view schemas. Generate the HTTP calls from openapi.json and write the socket layer against asyncapi.json. The socket contract is the rule a frame changes by.

Keep it current

Regenerate whenever a route changes, and commit the document beside the code that changed it. pithy contract writes only when the bytes changed, so it is safe to run on every build, and a CI step can hold it fresh:

pithy contract
git diff --exit-code -- 'apps/*/openapi.json' 'apps/*/asyncapi.json'

A Worker older than your client is the case to plan for. A response field added in a later release arrives as optional, so a client generated from a newer document still reads an older Worker’s answer instead of failing it.

What a generated client is never asked to do

Validate a token, know a SKU, or name a storage key. Those stay on the server, for the reasons the front-ends overview gives. The platform pages — native iOS, native Android, Unity, Godot and Unreal — cover what the document cannot: where each platform keeps the session token, and how a purchase reaches the entitlement.

ESC