Skip to content

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.

otp.com Team
One spec document fanning out into four language SDK cards via thin green lines

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 entered
  • resend: retry on the next channel
  • status: 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. verify never returns the code (you match on otpId), 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.