IBM i APIs & Integration
When One API Isn't One Program: The Hard Part of Contract Fidelity on IBM i
A REST endpoint on IBM i rarely maps to a single program. This article examines how contract fidelity becomes a distributed problem across RPG call chains, behavioral drift, and external services.
There’s a version of the “expose RPG as an API” story that’s genuinely solvable today, and a version that isn’t. Most of the tooling conversation lives in the first. The interesting problems live in the second. This article is about the boundary between them.
The easy case: one program, one contract
Start with the case that works. You have a single RPG procedure — a well-defined interface, typed parameters, a clear return. You want to expose it as a REST endpoint with an OpenAPI contract that actually describes it.
This is tractable. Everything you need is in the RPG source: the parameter list, the data types, the structures. A contract derived directly from that source — rather than hand-written alongside it — stays faithful, because there’s a single source of truth and a single place where change happens. When the procedure changes, the contract can be regenerated. When they disagree, you can detect it.
I’ve spent a lot of time in exactly this territory, and I want to be clear that it’s real work — RPG’s type system, decimal precision, indicators, varying fields, and included definitions make “faithful” harder than it sounds. But it’s a bounded problem. The truth lives in one place, and one place can be kept honest.
The trouble starts the moment that stops being true.
The real case: one endpoint, many programs
In practice, a single API endpoint rarely maps to a single RPG program.
Consider an endpoint that creates an order. Behind that one REST call, on the IBM i side, there may be several programs working together: one validates the customer, one checks stock availability, one calculates pricing, one writes the order record. Maybe one of them, in turn, calls an external service — a payment authorization, or another internal system reached over its own API.
The endpoint is not a program. It’s an orchestration.
And its contract — what it accepts, what it returns, which errors it can raise, what it guarantees — depends on the combined behavior of everything it touches. Not one source member. Several. Sometimes several that aren’t even RPG.
This is where “keep the contract faithful to the code” stops being a single-file problem and becomes a distributed one. And distributed problems don’t yield to the same tools.
Three layers, three different degrees of difficulty
It helps to separate what’s actually going on, because these layers aren’t equally hard.
Layer one: structural consistency across RPG programs
If program A passes a data structure to program B, and B hands part of it to C, does that structure stay coherent along the chain, and does the published contract still reflect it? This is harder than the single-program case, but it’s tractable — because it all still lives inside the RPG world that static analysis can see. You can, in principle, follow the calls and the structures. It’s more work, not a different kind of work.
Layer two: behavioral drift
This one is subtler. Suppose the “check stock” program, buried two calls deep, gains a new error case — a partial-availability condition it didn’t used to return. No data structure changed. Every type is the same. But the contract of the top-level endpoint did change: it can now surface an outcome it couldn’t before. Consumers built against the old contract don’t know that outcome exists. Structural analysis won’t catch this, because structurally nothing moved. The drift is in behavior, not shape.
Layer three: external services
Here the ground gives way. The moment the orchestration reaches a service outside the IBM i — a payment gateway, a downstream system, anything you don’t compile — the truth of the contract stops being visible to any tool analyzing your RPG. That external service can change its response format, add an error, tighten a validation, and your API’s real contract changes with nothing in your source moving at all. Static analysis of RPG cannot reach across that boundary, because the relevant code isn’t yours and isn’t RPG.
Being honest about the boundary
I think it’s worth stating plainly where the line falls, because pretending it isn’t there helps no one.
Layers one and two are, broadly, inside the reachable world. A tool that understands RPG deeply can, at least in principle, follow structures across programs and reason about which changes affect a published contract. It’s hard, and I won’t pretend the general case is a solved problem — but it’s the kind of problem static analysis is suited to.
Layer two is only partially reachable, and the boundary is worth being precise about. Behavioral drift becomes visible to analysis exactly to the extent that behavior has been made explicit somewhere a tool can read: declared error conditions, annotations carried in the source, contract tests that encode expected outcomes. Where that semantic context exists, drift is detectable — a returned condition outside the declared set is a difference a tool can see. Where it doesn’t, the behavior lives only in the control flow and in the developer’s head, and no amount of structural parsing will surface it. So layer two isn’t purely a static-analysis problem or purely a runtime one; it’s a question of how much of your intent you’ve been willing to write down.
Layer three is different in nature. Once a contract’s truth depends on a system you can’t see, no amount of RPG analysis will make it visible. That’s not a tooling gap to be closed with a better parser; it’s a structural limit. The honest answer there isn’t “our tool handles it” — it’s “this is where static guarantees end and runtime observation, contract testing, and organizational discipline have to take over.”
I’d rather say that clearly than oversell a clean solution to a problem that doesn’t have one yet.
A direction, not a destination
If there’s a way through, my instinct is that it’s compositional rather than monolithic.
If every unit-level contract — each individual program’s interface — is trustworthy, versioned, and kept faithful to its source, then the confidence you can place in an orchestration is a function of the confidence in its parts. You can’t trust the whole if you can’t trust the pieces; but if you can trust the pieces, composing landscape-wide confidence from them becomes a tractable engineering problem rather than a hopeless one. The unit-level guarantee isn’t the whole answer, but it looks like the necessary foundation the rest is built on.
That still leaves the external-service boundary, and behavioral drift buried deep in a call chain, as genuinely open problems. I don’t have clean answers to those, and I’m suspicious of anyone who claims to.
Over to the community
So here’s the question I’d put back to the people who run these systems every day: when an API on your IBM i spans several programs — and maybe reaches outside the box entirely — how do you keep its contract honest over time?
Do you lean on contract testing? Runtime monitoring of the business outcomes, the way some teams are starting to watch business metrics rather than just system metrics? Manual discipline and good change control? Something else entirely?
Keeping a single contract faithful to a single program is the part I’d call solved-in-principle. Extending that confidence across an entire integration landscape is, to me, one of the more interesting unsolved problems in IBM i modernization — and exactly the kind of thing this community is well placed to work out together.

Comments
Share your thoughts, questions, or real-world IBM i experiences related to this article.