--- name: ndb2-api description: >- Describes how mission-control integrates with the Off-Nominal NDB2 HTTP API and webhooks: V2 (preferred) vs legacy V1 paths, shared client, error shapes, and webhook routes. Use when adding or changing NDB2 calls, webhooks, predictions, bets, votes, scores, or migrating from V1 to V2. --- # NDB2 API (mission-control) NDB2 is the predictions backend. This repo calls it over HTTP and receives webhooks. **Prefer V2** for new work; V1 remains for endpoints not yet moved. ## Configuration - `mcconfig.ndb2.baseUrl` — API origin (`NDB2_API_BASEURL`). - `mcconfig.ndb2.clientId` — Bearer token for API and webhook auth (`NDB2_CLIENT_ID`). All outbound calls use `Authorization: Bearer `. ## Types package Shared contracts live in **`@offnominal/ndb2-api-types`** (see `package.json` version). - **V2:** import from `@offnominal/ndb2-api-types/v2` (e.g. `Entities`, `Endpoints`, `Webhooks`, `Utils`). - **V1:** local types in `src/providers/ndb2-client/types.ts` under namespace `NDB2API` (and enums like `PredictionLifeCycle`). ## HTTP client **`src/providers/ndb2-client/index.ts`** — class `Ndb2Client`, default export `ndb2Client` (constructed with `mcconfig.ndb2.clientId`, `initialize()` loads seasons from V2). Path convention: `new URL(baseURL)` then `url.pathname = "api/v2/..."` or `"api/..."` (V1). ### V2 endpoints (use these when available) | Method | Path | Client method | Notes | |--------|------|---------------|--------| | GET | `api/v2/seasons` | `getSeasons()` | Cached on client after `initialize()` | | GET | `api/v2/predictions/:id` | `getPrediction(id)` | Returns `data` only | | POST | `api/v2/predictions` | `addPrediction(body)` | Body type: `Endpoints.Predictions.POST_Predictions.Body` | | PATCH | `api/v2/predictions/:id/retire` | `retirePrediction(id, discord_id)` | V2 retire | Successful V2 JSON responses are unwrapped to **`res.data.data`** in the client. Errors use **`handleError`**: response shape `{ success, errors: [{ code, message }], data }`; thrown value is a **`[userMessage, detailMessage]`** tuple (same pattern as other providers). ### V1 endpoints (legacy; still in use) Paths are under **`api/...`** (no `v2`). These methods use **`handleError_v1`**, which expects **`NDB2API.GeneralResponse`**: `{ success, errorCode, message, data }`. Still routed through `Ndb2Client` today: - `POST api/predictions/:id/bets` — `addBet` - `POST api/predictions/:id/votes` — `addVote` - `POST api/predictions/:id/snooze_checks/:snoozeCheckId` — `addSnoozeVote` - `POST api/predictions/:id/trigger` — `triggerPrediction` - `GET api/users/discord_id/:discord_id/scores` — `getScores` - `GET api/predictions/search` — `searchPredictions` (`SearchOptions`, `SortByOption`) - `GET api/scores` (with `view=points|predictions|bets`, optional season) — leaderboard helpers - `PATCH api/predictions/:id/snooze` — `snoozePrediction` When migrating a call from V1 to V2, switch the path, response unwrapping (`data` vs full body), error handler (`handleError` vs `handleError_v1`), and types to the V2 package. ### Example: creating a prediction (V2) Service pattern: `src/services/ndb2/add-prediction/index.ts` builds `NDB2API.Endpoints.Predictions.POST_Predictions.Body` with `discord_id`, `text`, `date` (ISO string), `driver` (`"date"` | `"event"`), then `ndb2Client.addPrediction(body)`. ## Webhooks Router: **`src/services/ndb2/webhooks/index.ts`**. - **Auth:** `validateWebhookAuthorization` — `Authorization: Bearer` must match `mcconfig.ndb2.clientId`. - **V1 route:** `POST .../ndb2` — body must pass `isNdb2WebhookEvent` (`src/services/ndb2/webhooks/v1/types.ts`). Several event names are **short-circuited as ignored** before `handleV1Webhook` (see router): e.g. `new_prediction`, `untriggered_prediction`, `unjudged_prediction`, `retired_prediction` on this path. - **V2 route:** `POST .../ndb2/v2` — validate payload with **`API.Webhooks.isWebhookPayloadV2`** from `@offnominal/ndb2-api-types/v2`. Handler: `src/services/ndb2/webhooks/v2/index.ts` (e.g. `untriggered_prediction`, `unjudged_prediction`, `retired_prediction`). Middleware responds early with JSON **`"thank u"`**; do not assume the handler must send the HTTP response. ## Where to look in the tree - Client + V1/V2 split: `src/providers/ndb2-client/` - Webhooks: `src/services/ndb2/webhooks/` (`v1/`, `v2/`, `middleware.ts`) - Feature services: `src/services/ndb2/` (e.g. `add-prediction`, `retire-prediction`) When the OpenAPI or `ndb2-api-types` package changes, update call sites and this skill if behavior or paths shift.