Part 8 of 12 – Conversation Intelligence Platform series Back to Part 1
The Conversation Intelligence platform didn’t get built on top of our existing API. It got built alongside a replacement for it – and understanding why we replaced the architecture matters if you’re going to build on what we’ve shipped.
The existing Wholesale API at api.simwood.com/v3 has served us well for a long time. It also has approximately 200 endpoints, no independent versioning, monolithic documentation that nobody enjoys reading, and an authentication model that involves passing credentials through a central proxy. Every service on the platform is coupled to every other service through that proxy. Teams cannot ship independently. The API surface has expanded without bound. The documentation is a perpetual backlog.
This is a familiar failure mode for any API that grows organically over years without architectural revision. The solution is also familiar: decompose it. But decomposing a 200-endpoint API that customers are actively using, without breaking them, requires a plan rather than a rewrite.
The new architecture is built around three components.
Traefik is the gateway. It handles routing, TLS termination, and rate-limiting. That’s all it does – it doesn’t know anything about authentication or business logic.
The ForwardAuth shim sits between Traefik and the upstream services. When a request arrives, Traefik calls the auth shim to validate the credentials. The shim validates and injects a set of standard claims headers – account identity, scopes, tier, and key identifier – into the request. Traefik then forwards the enriched request to the upstream service.
Each service reads those headers and trusts them. No service calls a central auth service at runtime. No service re-validates credentials. The gateway handles authentication once; the service handles authorisation locally using the claims it receives.
The result is that services are completely decoupled from each other and from the auth infrastructure. A service doesn’t need to know whether the client authenticated with an API key, a Keycloak JWT, or (for legacy clients) Basic Auth. It receives claims, it checks scopes, it executes the request, it returns a result.
The v3 Wholesale API continues to run unchanged on this infrastructure. It routes through Traefik and ForwardAuth exactly like the new services, gaining all the security improvements without any changes to its own codebase or URL structure. Nothing stops responding overnight. The v3 path stays stable for each capability until that capability’s deprecation window closes.
New capabilities are never added to v3. They’re built as independent services. And existing v3 capabilities are being progressively re-expressed as services: each domain becomes its own versioned service, and the corresponding v3 paths are then deprecated under the standard policy. The v3 decomposition map sets out the full picture – which domains become which services, in what order.
The target is twelve services, each independently versioned, each with its own OpenAPI spec and documentation. Numbers, Voice, Messaging, Billing, Account, Porting, Lookup, BYOC, Operator Connect, WhatsApp, Conversational AI, and Conversation Intelligence. Each one ships when it’s ready. Each one evolves on its own schedule.
Independent versioning is one of the things that sounds obvious in retrospect but is genuinely difficult to retrofit. In a monolithic API, a breaking change to one endpoint requires a version bump for the entire API surface. In the service model, a breaking change to the Voice service requires a new version of the Voice service – nothing else is affected. The Numbers team can ship a v2 of their API without consulting the Billing team. A customer using only Messaging doesn’t need to care about a Voice API change.
The URL carries the version: /intelligence/v1/, /intelligence/v2/. When a breaking change requires a new version, both versions run in parallel until the deprecation window for the old one closes. Part 10 goes into the deprecation policy in detail.
Every service in this architecture follows the same conventions. It reads claims from headers – never re-validating credentials itself. It filters every query by the injected account identity – never trusting account identity from request parameters. It enforces scopes on every operation. It exposes a live OpenAPI spec at /openapi.json. It versions independently on breaking changes only.
Those conventions aren’t suggestions – they’re how the architecture works. A service that trusts account_id from a query parameter rather than the injected claim is a security hole. A service that doesn’t expose an OpenAPI spec can’t be included in the documentation portal. The conventions are what make the system coherent rather than a collection of loosely related services.
The practical benefit for customers is that the API surface becomes manageable. Instead of one enormous reference document covering 200 endpoints, there’s a service index with clear scope, a per-service reference that covers only what that service does, and a documentation portal (covered in Part 11) that lets you explore and try things interactively.
The practical benefit for us is that teams can ship. A team working on the Voice service doesn’t need to coordinate with a team working on Messaging. The gateway handles the plumbing; the services handle the business logic; everyone ships at their own pace.
Previous: Part 7 – Conversation Memory and vCon