# The three API tiers

xly does not have one API — it has **three**, hosted in three independent
Spring Boot services with different audiences and different operational
contracts. Knowing which one you're talking to is the first step in any
integration conversation.

| Tier | Service | Context path | Audience |
|---|---|---|---|
| **Internal** | `xlyEntry` | `/xlyEntry` | The xly SPA itself (BACK + FROUNT). Not a stable contract for external callers. |
| **External** | `xlyApi` | `/xlyApi` | Tenants and integrators calling xly from outside. The data-driven public API. |
| **Inbound webhooks** | `xlyInterface` | `/xlyInterface` | Third-party systems pushing events into xly. Carries Swagger. |

Each service builds to its own WAR and runs in its own JVM. They share
nothing in process; they share **the database**, and that shared
database is what makes their separation work — internal-API writes show
up to external-API reads automatically because both run against the
same schema.

## Why three tiers (and what splitting them costs)

Each tier was originally split off to answer a different question:

- **Internal** is large (universal CRUD over all metadata-driven
  modules), volatile (changes when the framework changes), and
  intentionally untyped (the SPA decides what to ask for, server obeys).
- **External** is curated (only the endpoints integrators are allowed
  to use), versioned by `sApiCode`, and authenticated with bearer
  tokens.
- **Inbound webhooks** receive untrusted bodies from third-party
  systems and route them to xly handlers. The Swagger UI lives here
  because that audience benefits most from interactive documentation.

The split has real costs that the wiki should not gloss over:

- **Three WARs to deploy, monitor, and version-pin.** A new release
  has to ship coordinated builds of `xlyEntry`, `xlyApi`, and
  `xlyInterface`. Mismatches (e.g., a schema change in `xlyEntry`
  that `xlyApi` hasn't picked up) are silent until the call path
  hits them.
- **Duplicate code.** `RequestAddParamUtil` exists in both
  `xlyPersist` (for `xlyEntry`) and `xlyApi` (near-identical 56-vs-57
  line copy). `InterfaceController` exists in both `xlyApi` and
  `xlyInterface` with overlapping `/interfaceDefine/callthirdparty/*`
  endpoints. Keeping the two halves in sync is operational discipline,
  not a compile-time guarantee.
- **No shared session.** A user authenticated in BACK has no
  session in `xlyApi` — external callers fetch a separate bearer
  token. This is correct for *external* integrators but means
  internal cross-WAR calls (rare in practice, common in temptation)
  have to go through the public token flow.
- **Three context-paths means three reverse-proxy entries.**
  The mapping from `BACK=:8597` and `FROUNT=:8598` to the actual
  WARs lives in nginx config that isn't in this repo. Misconfigured
  proxies are a common failure mode the codebase can't catch.

Could the split have been a single deployable with internal package
boundaries? Yes — Spring Boot supports it. The benefit of that
alternative would be: one build, one set of dependencies, one
session story, no duplicate utility classes. The cost: harder to
scale tiers independently, harder to rate-limit external callers
without affecting the SPA. xly chose the deployment-time isolation;
the wiki's job is to acknowledge what that choice traded away.

## What each tier looks like at runtime

- **Internal** — see [the five-key read](../reference/maintainer/runtime.md#the-five-key-read). One
  endpoint (`/business/getModelBysId`) returns the entire form layout;
  another (`/business/addUpdateDelBusinessData`) writes any row in any
  table the metadata names. Few endpoints, generic shapes.
- **External** — most calls go through `/api/invoke/{sApiCode}`. The
  `sApiCode` is a row in the `sysapi` metadata table that names the
  SQL template, parameters, auth requirement, and target. New external
  APIs are *registered as data*, not as code — the same data-driven
  thesis the framework applies to its own forms.
- **Inbound webhooks** — `/interfaceDefine/invoke/{interfaceInvoke}`
  receives a payload, looks up the matching handler in metadata, runs
  the configured SQL or stored procedure. Plus a few hard-coded
  receivers (`/Push`, `/Pull`, `/send/sendQw`) for specific partners.

## Where to look next

- [API Reference (Internal)](../api-reference/internal.md) — the
  `xlyEntry` endpoints the SPA uses; not for external callers.
- [API Reference (External)](../api-reference/external.md) — the
  `xlyApi` surface integrators consume, and the `sysapi`-driven
  `/api/invoke` pattern.
- [API Reference (Webhooks)](../api-reference/webhooks.md) — the
  `xlyInterface` inbound surface and where Swagger UI is served.
- [API Reference (Messaging)](../api-reference/messaging.md) — the
  ActiveMQ / RocketMQ event channels that complement the HTTP APIs.
