# Legendum SDK — Integration patterns

You have read [legendum.md](legendum.md) and understand the primitives (`charge`, `reserve`, `tab`, OAuth, `linkKey`, middleware). This document answers a different question: **which combination fits your product**, and **what to implement first**.

**Audience:** a developer choosing an architecture before writing routes and UI.

---

## 1. The two axes

Almost every integration sits somewhere on these axes:

1. **How does the user authenticate to *your* app?**  
   Legendum can be **only a billing provider** (you already have accounts) or **both IdP and billing** (Legendum OAuth establishes who they are).

2. **How does billing attach to that identity?**  
   Either **in the same step as login** (login-and-link) or **later** (browse first, link credits in settings).

Everything below is a variation on those choices.

---

## 2. Flavor A — “Login-and-link” (smallest web app)

**When it fits:** New or greenfield web apps where Legendum is the identity provider *and* you want billing from day one.

**Flow:** On “Sign in”, your server calls `requestLink()`, then builds `authAndLinkUrl({ redirectUri, state, linkCode })` and redirects the browser. On the OAuth callback, `exchangeCode()` returns `email` plus **`account_token`** when linking succeeded.

**What you persist:** `email` (identity), and the billing credential from **`data.account_token`** (your DB column can use another name — see [legendum.md](legendum.md) §9). Update it whenever the exchange returns a new one.

**What you might skip:** A separate “link billing” page, if every user is expected to complete login-and-link in one go.

**Caveat:** If a user completes OAuth but does **not** end up linked (e.g. they cancel the pairing side), `exchangeCode` may show `linked: false` — decide whether to block features or show a “finish linking” path (see Flavor B).

---

## 3. Flavor B — “Identity first, link later”

**When it fits:** You want **Login with Legendum** for accounts, but credits are optional, or onboarding should be short.

**Flow:** Use `authUrl()` / `exchangeCode()` for **identity only**. Persist `email` from the exchange. **Linking** happens in a second phase: `middleware()` at a prefix (e.g. `/legendum`) plus `linkWidget()` or `linkController()` so the user can pair in a popup or full-page flow when they are ready.

**What you persist:** Same as A, but **`account_token`** may be absent until they complete linking.

**Why people choose it:** Clear separation between “who you are” and “whether this app may charge your Legendum balance.” Slightly more UI and routing than Flavor A.

---

## 4. Flavor C — “Existing users, add Legendum billing”

**When it fits:** You already have sessions, passwords, or another IdP; you are **not** switching primary login to Legendum.

**Flow:** Do **not** rely on Legendum OAuth for primary auth unless you intentionally migrate. Use **`middleware()` + `linkController()`** (or `linkWidget()`): your logged-in user starts a pairing from your settings page, your backend calls `requestLink()` / `pollLink()` / `waitForLink()` as exposed by the middleware routes, and you store **`account_token`** (or your own column name) for that app user.

**Optional:** `startAuthAndLink()` on the controller if you want one browser trip that both signs them into Legendum (or confirms identity) **and** pairs — useful when the same human does not yet have a local account tied to Legendum OAuth in your DB.

**What you persist:** Map your primary user id → stored billing token (from **`account_token`** on the wire). Never use that token as the primary key; use your own user id plus optional Legendum `email` for display.

---

## 5. Flavor D — CLI, agents, headless tools

**When it fits:** No browser, or automation that cannot complete OAuth.

**Flow:** User generates an **account key** (`lak_…`) at legendum.co.uk and pastes it once. Server calls `linkKey(accountKey)` with your service credentials; response includes `{ account_token, email }`. Persist the billing credential; treat **`lak_`** like a password — do not log it.

**Relationship to web:** Many products offer **OAuth on the web** and **`linkKey` on the CLI** for the same backend — both produce a per-service token scoped to your API key.

**Advanced:** `account(lak_…)` is for tooling that **acts as the user on Legendum** (balance, transactions, pairing from the Legendum side). It is **not** the usual path for charging your own product’s usage.

---

## 6. Flavor E — “API keys” or multi-tenant tokens

**When it fits:** Your product’s unit of access is an **API token**, a **workspace key**, or another row that is **not** “one human = one user row”.

**Pattern:** Store the **billing token** (`account_token` from Legendum) on that credential row (the thing that hits your API), not only on a profile table. Wire **`middleware({ getToken, setToken, clearToken })`** so `getToken` resolves the current token from **whichever key** authenticated the request.

**Why:** The SDK does not care whether the token is attached to a user id or an API key id; your callbacks must implement that mapping. You may still expose `linkController` in a dashboard that is scoped to “this API key” or “this project.”

**Complexity:** Unlink, balance UI, and `token_not_found` handling multiply with the number of credentials — plan `clearToken` and UX accordingly.

---

## 7. Choosing billing primitives (short guide)

Align with [legendum.md](legendum.md) section on billing primitives; at a high level:

| Situation | Prefer |
|-----------|--------|
| Fixed cost per operation | `charge` |
| Cost unknown until work finishes (LLM, jobs) | `reserve` → `settle` or `release` |
| Many tiny units (fractional accumulation) | `tab` — **one long-lived tab per `(token, description)` in a process**, not per HTTP request |
| Showing UI or health checks | `balance` |

**Heavy integrations** often combine **`reserve`** for variable-cost work and **`tab`** for high-frequency micro-units, with configuration for thresholds and flush behavior.

---

## 8. Hosted vs self-hosted / feature gating

A common pattern is: **if `LEGENDUM_API_KEY` (and secret) are absent**, treat the deployment as **self-hosted** or **billing off**: skip OAuth to Legendum, skip charges, and either disable credit-gated features or run them without billing.

**Important:** This is a **product decision** — document it clearly so tests and local dev behave predictably. Use `legendum.isConfigured()` (or equivalent checks) before calling charge paths.

---

## 9. Recommendations (practical defaults)

1. **Prefer login-and-link (Flavor A)** when Legendum is both IdP and billing — fewer screens and less support burden than identity-only plus a separate linking story.

2. **Use identity-first (Flavor B)** when billing is genuinely optional or you need a shorter first-time path.

3. **Persist `account_token`** from `exchangeCode` / `linkKey` and **refresh it on every successful exchange** when present — tokens rotate on re-link.

4. **Implement `clearToken`** in middleware (or equivalent) so **`token_not_found`** clears stale storage and your UI can prompt to re-link — see [legendum.md](legendum.md) edge cases.

5. **Keep `email` in sync** if you store it: OAuth returns the current verified email; some teams also update from **`charge` / `settle` responses**, which include `email` for the transaction. Do not treat email as immutable without a refresh strategy.

6. **Never put `LEGENDUM_SECRET` in the browser.** Prefer `mountAt` + `middleware()` so pairing and status routes run server-side; only `linkController` and static URLs belong in the client.

7. **Test with `legendum.mock()`** so CI does not depend on the live API — [legendum.md](legendum.md) describes the contract.

---

## 10. Where to read next

| Topic | Document |
|--------|----------|
| API surface, linking flows, billing primitives, errors | [legendum.md](legendum.md) |

If you are stuck between Flavor A and B: **start with A**; add a dedicated linking/settings path only when you discover a real cohort that completes OAuth without linking.
