It’s certainly tractable, but the amount of effort can vary significantly on the amount of difference between the two versions.

In the simplest case, you can start by duplicating all of your existing API code, and make it addressable as a new endpoint (e.g. https://example.com/v2).

Now imagine you make a change like renaming all the API operations or all the parameters. You can make this directly in the v2 code without changing anything else; and you can maintain the two APIs in parallel like this indefinitely.

However, the maintenance costs of this approach can become significant over time, e.g.

1) Perhaps you develop a new feature, made available as a new API operation. Do you need to implement this for both API versions? Maybe some customers are still using V1 and they want the feature. Depending on how much V1 and V2 implementations have drifted, this effort might be significantly more than copying the code.

1b) And anytime you duplicate effort like this, you may also need to duplicate tests, canaries, monitors, client SDKs, and any other moving part.

2) If you radically change the data layer underneath the API, or some other critical aspect of its implementation, then you may need to make that change twice, in both V1 and V2 — unless it is encapsulated sufficiently such that the code for both can call it.

For example, if you have decided that you need to add caching to a common read API, then you may need to implement that in both of the V1 and V2 code path (unless the implementation of both versions calls some module common to both, and you can change this module to add caching - but this isn’t always possible).

3) There are some crosscutting concerns that inevitably affect all API versions. For example, if you are changing the way that authorization logic works, or changing the way that actions interact with resources, then for correctness/safety/security reasons, it may be necessary to ensure that all API versions operate identically. If you build a new access control feature for the V2 API, then that doesn’t do you any good if the V1 API ignores the same constraints. The same may also be true for observability changes like logging and monitoring — you may need to apply them to all APIs.

If you have sufficient reason to make a breaking change to the API, then it is likely that whatever motivated the new API will also cause their implementations to begin drifting apart. The more they drift, the greater the maintenance effort can be.

On the other hand, if the design of your system is such that you can largely leave the code for V1 alone, and you are sure you won’t need to change V1 even as you are significantly improving V2 (including all downstream dependencies, which the V1 code might also call into) then it might not be so problematic to operate multiple API versions this way. You would also duplicate all of your tests, etc. and leave the ones for V1 alone.

Indeed — if this is what your comment is getting at — in some systems you could conceivably implement the V1 API in terms of a call into the V2 code, as a sort of compatibility layer. (And that would also be a safer approach, if it is feasible, with respect to crosscutting concerns like authorization)

If you do end up actively maintaining and developing both API versions, then the total effort can in some cases become greater than 2x the cost of one API version, due to the additional effort of making sure that both code paths work correctly for all possible interactions.

If you end up having this conversation with someone in the future, then ask why the effort is greater than copying the V1 code to a new V2 endpoint and proceeding to change V2. This should focus the conversation on the specific implementation challenges unique to the situation.