Skip to content

API Interface Strategy — REST, gRPC, GraphQL

Ref: suitability-engine-arch-2 · Capability: SUITABILITY-ENGINE · Status: draft Repo: suitability-engine

Three interface definitions now exist for the Suitability Engine’s export operation, same capability, three wire protocols:

Status: interface definitions only. The only one with a real server behind it today is REST (src/server/app.ts, plain Node http). gRPC and GraphQL are contracts to implement against once a concrete internal consumer needs them — not built speculatively ahead of a real one. Building an unused server is real maintenance cost (dependency surface, a second auth path to secure, a second thing to keep in sync with SuitabilityData as it evolves) for zero benefit until something actually calls it.

The original ask assumed Swagger 2.0. Swagger 2.0 was superseded by OpenAPI 3.0 in 2017, and OpenAPI 3.1 (2021) is current — it merged the schema format with JSON Schema, so $ref/nullable/etc. work the way any modern JSON Schema tooling expects. api/openapi.yaml is written against 3.1. No reason to target the older spec for new work.

Decision — which protocol for which consumer

Section titled “Decision — which protocol for which consumer”
ConsumerProtocolWhy
Origo (external, loosely-coupled partner)REST / OpenAPIExternal partners integrate fastest against REST — every language has an HTTP client, no codegen toolchain required on their side, human-readable payloads for debugging a partner integration over email/support tickets. This is also the only protocol with per-request scoped-API-key auth designed for it (docs/external/origo-suitability-and-adviser-api-design.md §5.1 in client-onboarding) — gRPC/GraphQL auth for an external partner would need separate design work, not done here.
data-backbone, other tightly-integrated internal WTP servicesgRPC (default recommendation)Codegen’d, strongly-typed clients; binary framing is real overhead-reduction for high-volume internal calls; a single point-to-point call per client record (fetch one report) is gRPC’s actual sweet spot, not GraphQL’s.
An internal dashboard/aggregation surface needing several reports with flexible field selection in one round tripGraphQL (situational, not default)Only reach for this if the actual access pattern is genuinely multi-report/flexible-field (schema.graphql’s suitabilityReports batch query exists for exactly this). If a consumer just wants one report per call, gRPC is simpler and doesn’t need a resolver layer.

Do not build all three defensively. Pick the protocol the actual consumer’s access pattern calls for, using the table above, and implement that one. The two unbuilt schemas exist so the shape is already decided and reviewable — not so all three get built regardless of whether anything calls them.

Right now: by hand. All three were authored together on 2026-08-16 against the same source of truth (suitability-template/contract.ts, suitability-rules/types.ts). This will drift the first time SuitabilityData changes and only one schema gets updated — a known, accepted risk for now, not solved here. If a second protocol actually gets implemented (per the decision above), that’s the moment to invest in codegen (e.g. generate .proto/.graphql from the Zod schema, or a shared IDL all three generate from) rather than three hand-maintained copies indefinitely.

What CRM-bridge’s interface strategy should be

Section titled “What CRM-bridge’s interface strategy should be”

Not defined here — SPEC-153 (the CRM/Adviser API) has no external shape yet beyond an inbound-only pipeline for one platform (Plannr). Defining REST/gRPC/GraphQL contracts for an API that doesn’t have a stable bidirectional design yet would be designing against a moving target. Once that design stabilises (see the CRM-bridge separation decision), the same three-protocol pattern established here is the template to repeat, not a new approach to invent.