How We Generate Our SDKs from One OpenAPI Spec
Why the otp.com client libraries for Node, Python, Go, and PHP are generated from one OpenAPI spec instead of written by hand, and how that stops drift.
Hand-written client libraries are a maintenance trap. Ship a new field on the API and you now have four libraries to update by hand, four changelogs to write, and four chances to drift apart from what the docs promise and what the client actually sends.
So we don’t write our SDKs by hand. There is one source of truth, the public OpenAPI spec, and the Node.js, Python, Go, and PHP clients are generated from it.
One spec, four clients
The API exposes four actions, and so does every SDK:
send: send a code (your account routing picks the channel)verify: check the code the user enteredresend: retry on the next channelstatus: look up an OTP’s state
Generate from the spec and each language gets those four as typed methods (sendOtp, verifyOtp,
resendOtp, getOtpStatus in Node; send_otp, verify_otp, … in Python). Auth is the same
everywhere: your API key as a Bearer token, base URL https://api.otp.com/api/v1.
import { Configuration, OTPApi } from '@otp.com/sdk-node';
const api = new OTPApi(new Configuration({ accessToken: process.env.OTP_API_KEY }));
const sent = await api.sendOtp({ sendOtpRequest: { recipient: '+14155552671', locale: 'en' } });
const result = await api.verifyOtp({ verifyOtpRequest: { otpId: sent.otpId, code: '123456' } });
// result.matched === true
How the pipeline works
The flow has one direction: the spec describes the API, a generator turns it into client code for each target language, and those clients get published to their ecosystems (npm, PyPI, Go modules, Composer). Nobody edits the generated output by hand between those steps, because a hand-edit is a drift waiting to happen: the next regeneration would either overwrite it or, worse, be held back to preserve it. The spec is the code’s parent, not its sibling.
That constraint is the whole point. It means the request and response shapes in every SDK are the same shapes the API actually serves, because they came from the same document the server is built against.
Why generated beats hand-written
- No drift. The client’s request and response types are the spec. If the wire format changes, the types change with it, so a mismatch fails to compile instead of failing in production.
- Full coverage, instantly. A new parameter or endpoint appears in every SDK the moment the spec does, not whenever someone gets around to porting it.
- One review surface. Behavior is reviewed once, on the spec, instead of re-litigated in four languages.
- Honest types.
verifynever returns the code (you match onotpId), and the generated types reflect that; you can’t accidentally depend on a field that doesn’t exist.
When the API changes
Adding a field is the routine case, and it shows the model at its best. The spec gets the field, the
clients regenerate, and the new option is available in Node, Python, Go, and PHP in one pass, with
its type, not four separate ports landing weeks apart. Because the API is versioned under /api/v1,
changes are additive within a version, so regenerating picks up new capability without breaking the
method calls you already wrote.
The trade-off
Generated code isn’t always the most idiomatic code a human would hand-write for a given language, and we take that on purpose: predictable and in-sync beats hand-crafted and drifting. Where ergonomics matter, the method shape stays close to the REST call you’d otherwise make yourself, so the mental model transfers whether you read the SDK or the raw endpoint.
The spec is public
Because the OpenAPI document is public, you are not limited to the four languages we publish. If you work in a language we don’t ship, you can point a generator at the same spec and get a typed client of your own, or simply call the REST API directly, which works everywhere. The generated SDKs are a convenience built on an open contract, not a gate in front of it.
Getting a client
- Node.js:
npm install @otp.com/sdk-node - Python:
pip install otp-sdk - Go:
go get github.com/otp-com/sdk-go - PHP:
composer require otp-com/sdk-php
If your language isn’t on the list, the REST API works everywhere. Building with an AI agent instead of writing integration code? The MCP server exposes the same four actions as agent tools.