TL;DR: A single OpenAPI spec can be turned into a typed intermediate representation that drives language-specific SDK generation, reducing drift by making the spec the source of truth and the emitter the only language-specific layer, according to WorkOS. The real lesson is that generated code quality depends on disciplined schema design, not just tooling.
At a glance
What this is: This is a technical guide to building a custom SDK generator from OpenAPI with oagen, centered on a typed intermediate representation that prevents drift between API changes and generated clients.
Why it matters: It matters because identity and security programmes increasingly depend on machine-readable contracts, and the same governance discipline that reduces SDK drift also improves control over NHI, workload identity, and autonomous tooling ecosystems.
By the numbers:
- Only 44% of developers are reported to follow security best practices for secrets management, exposing a significant developer behaviour gap.
👉 Read WorkOS' guide to building a custom SDK generator with oagen
Context
OpenAPI-driven SDK generation fails when teams treat code generation as a one-off template problem instead of a contract problem. The issue is not just speed, but drift: every manual translation from API spec to language-specific client increases the chance that parameters, types, and behaviour diverge across runtimes.
For IAM and security teams, the parallel is familiar. Non-human identity systems, workload identities, and agentic tooling all behave better when a single source of truth drives downstream execution, because lifecycle, access shape, and policy interpretation are easier to control when the contract is typed and centralized.
Key questions
Q: How should teams prevent drift when generating SDKs from OpenAPI specs?
A: Treat the OpenAPI spec as the single source of truth, resolve it into a typed intermediate representation, and make every language backend consume that same model. Drift falls when naming, typing, and schema normalization happen once rather than being reinterpreted in each emitter.
Q: Why do typed intermediate representations matter in code generation pipelines?
A: A typed IR makes schema meaning explicit before rendering begins, which reduces ambiguity and catches unsupported variants early. That matters because every downstream client depends on the same resolved contract, not a best-effort interpretation of raw YAML or JSON.
Q: How do teams know if a generator is safe enough to maintain long term?
A: Look for exhaustive type handling, deterministic output, and fixture-based tests that cover edge cases such as nullability, unions, and pagination. If the generator can fail the build when a new schema shape appears, it is far more maintainable than one that guesses.
Q: What is the difference between a source schema and generated SDK code?
A: The source schema defines the contract, while generated SDK code is an implementation artefact derived from that contract. If teams edit generated clients by hand, they reintroduce drift and make lifecycle control harder across every language target.
Technical breakdown
Typed intermediate representation and API contract fidelity
oagen parses OpenAPI into a typed intermediate representation, or IR, before any language-specific code is emitted. That IR resolves references, normalizes schemas, groups operations into services, and turns raw spec data into a stable contract between parsing and generation. The important architectural point is that emitters do not re-interpret YAML. They consume resolved types such as primitives, arrays, models, enums, nullable values, unions, and maps. This removes a whole class of drift caused by each language backend inventing its own interpretation of the same schema.
Practical implication: keep the contract clean at the spec layer, because emitter quality will only be as good as the IR it receives.
Type narrowing and exhaustive emitter switches
The generator pattern depends on TypeScript's discriminated unions and exhaustive switching over ref.kind. That means each emitter can fail at build time if the IR adds a new variant and the renderer has not been updated. This is a compile-time safety property, not a runtime convenience. It is especially useful in code generation because unsupported schema shapes are caught before broken SDKs ship. The nullable case also shows why the IR must stay abstract: Python and TypeScript render nullability differently, but the same IR can support both cleanly.
Practical implication: write emitters so build failure is the warning system, not runtime surprises in generated clients.
Deterministic rendering of models, enums, and clients
The tutorial shows how models become dataclasses, enums become str, Enum classes, and resources become service classes with methods derived from operations, parameters, and request bodies. This is the operational layer where generator decisions matter most. Field ordering, import selection, pagination handling, retry behaviour, and method naming are all emitted from the same resolved data model. The result is not just generated code, but reproducible code generation that can be tested with fixtures and golden files.
Practical implication: test the emitter like production code, because deterministic output is what makes SDK generation safe enough to maintain.
NHI Mgmt Group analysis
Typed contract generation is a governance pattern, not just a developer convenience. The article shows that a spec resolved into a typed IR becomes the single place where meaning is fixed before language-specific output is produced. That same pattern maps cleanly to identity programmes that need consistent treatment of service accounts, API credentials, and autonomous tool access across systems. When the contract is authoritative, downstream drift becomes a governance problem instead of a tooling mystery. Practitioners should treat typed contract enforcement as control architecture, not formatting.
Schema normalization is the real control point in any generation pipeline. Resolving references, collapsing schema variants, and deriving names centrally prevents each downstream consumer from making its own policy interpretation. That matters in security because fragmented interpretation is how access models, secret handling, and entitlement shapes diverge over time. The named concept here is contract drift resistance: the ability of one resolved model to prevent inconsistent downstream behaviour. Practitioners should design for one authoritative model and many deterministic renderers.
Emitter logic becomes the enforcement layer once the source model is trusted. In this architecture, the emitter decides how nullability, ordering, retries, and naming become concrete code in each runtime. That is the same separation identity teams should recognise in NHI and workload identity tooling, where policy definition and runtime enforcement are related but not identical layers. The value is not automation alone. The value is keeping policy translation explicit and testable. Practitioners should map where decisions are made versus where they are executed.
Generation systems expose the same failure mode as identity sprawl when ownership is unclear. The tutorial's drift examples are a software analogue of unmanaged identity estates, where manual updates across multiple consumers inevitably fall out of sync. Once the number of targets grows, consistency depends on disciplined lifecycle control, not memory or process heroics. That is why the operational lesson extends beyond SDKs. Practitioners should assume that every copied contract eventually becomes a governance gap unless one system owns the truth.
Build-time failure is a security property when the generated artefact has operational impact. A generator that stops at compile time on an unhandled type variant prevents silent corruption of downstream clients. The same principle applies when identity or secret tooling must never guess at missing states or partially resolved access data. The takeaway is simple: if a generated artefact can affect access, movement, or trust boundaries, undefined schema handling must fail closed. Practitioners should prefer explicit breakage over implicit drift.
From our research:
- Only 44% of developers are reported to follow security best practices for secrets management, exposing a significant developer behaviour gap, according to The State of Secrets in AppSec.
- Fragmentation matters too, because organisations maintain an average of 6 distinct secrets manager instances, according to The State of Secrets in AppSec.
- For a broader governance lens, see NIST Cybersecurity Framework 2.0 and map generated-client controls to repeatable build, test, and release practices.
What this signals
Contract drift resistance: the more systems that depend on a generated artefact, the more the governing question shifts from feature velocity to consistency under change. That is true in SDK generation and just as true in NHI estates, where one authoritative model reduces the odds of divergent entitlement behaviour across tools, services, and teams.
When 44% of developers follow secrets management best practices, the underlying pattern is not ignorance but uneven operationalization. The same failure appears in generation pipelines that allow manual edits after code is emitted, because the organisation loses the ability to prove that one contract still governs every runtime path.
Practitioners should expect more identity and security tooling to adopt typed IRs, codegen-like policy compilation, and deterministic enforcement models. The near-term task is to decide where contract resolution lives, which systems are allowed to render from it, and how changes are validated before deployment.
For practitioners
- Treat the spec as the control plane Make the OpenAPI document the only approved source of truth for method names, types, and service grouping, then forbid downstream hand edits to generated clients.
- Enforce exhaustive emitter coverage Use discriminated unions and compile-time checks so a new schema variant fails the build until every renderer branch is updated.
- Test generator output with golden fixtures Add fixture specs for edge cases such as unions, nullable fields, pagination, and enums, then compare emitted files byte for byte in CI.
- Separate contract resolution from language rendering Keep schema normalization, reference resolution, and naming in one layer, then let each emitter focus only on runtime-specific output decisions.
Key takeaways
- OpenAPI-based SDK generation works best when the spec is treated as the authoritative contract and every runtime client is derived from it.
- Typed intermediate representations reduce drift by centralising schema resolution, naming, and type interpretation before any language-specific output is produced.
- Teams that want maintainable generators need compile-time coverage, golden-file tests, and clear ownership of the contract layer.
Standards & Framework Alignment
This section maps relevant standards and security frameworks to the operational risks and controls described in this guidance.
OWASP Non-Human Identity Top 10 address the attack and risk surface, while NIST CSF 2.0 and NIST Zero Trust (SP 800-207) set the governance and control requirements practitioners need to meet.
| Framework | Control / Reference | Relevance |
|---|---|---|
| NIST CSF 2.0 | PR.DS-1 | Contract fidelity and deterministic builds support consistent data handling. |
| NIST Zero Trust (SP 800-207) | PR.AC-4 | Typed, centralised contracts reduce inconsistent access interpretation across systems. |
| OWASP Non-Human Identity Top 10 | NHI-03 | The article's secrets and automation parallels align with lifecycle control of machine identities. |
Apply NHI-03 to ensure generated or machine-driven identities are managed through a single lifecycle process.
Key terms
- Typed Intermediate Representation: A typed intermediate representation is a normalized data model created before code generation begins. It resolves raw schema input into explicit types, relationships, and metadata so downstream emitters do not need to re-interpret the source contract.
- Emitter: An emitter is the language-specific component that turns a resolved contract into code for one target runtime. It applies rendering rules, import decisions, and formatting choices while relying on the upstream model to define meaning and structure.
- Contract Drift: Contract drift is the gradual mismatch between an authoritative API specification and the code or behaviour generated from it. It usually appears when multiple teams manually translate the same source into different runtimes, creating inconsistent types, names, or handling rules.
- Golden-file Test: A golden-file test compares generated output against a committed expected result. In generator pipelines, it is a reliable way to prove that changes to parsing or rendering logic have not altered the emitted artefacts in unintended ways.
Deepen your knowledge
NHI governance, agentic AI identity, and machine identity lifecycle are core topics in our NHI Foundation Level course, the industry's only accredited NHI security programme. If you are responsible for identity security strategy or NHI governance in your organisation, it is worth exploring.
This post draws on content published by WorkOS: How to build a custom SDK generator with oagen. Read the original.
Published by the NHIMG editorial team on 2026-05-28.
NHI Mgmt Group — the independent authority on Non-Human Identity, IAM, and Agentic AI security. nhimg.org