You can generate a typed TypeScript client directly from the OpenAPI spec (the API Reference tab in the top navigation is generated from the same api-reference/openapi.json). Two common toolchains:

Option A — openapi-typescript + openapi-fetch

This is the lightest. No code generation, just types.
npm i -D openapi-typescript
npm i    openapi-fetch
Generate the types:
npx openapi-typescript https://docs.future-demand.com/api-reference/openapi.json \
  -o src/fd.types.ts
Use them:
import createClient from "openapi-fetch";
import type { paths } from "./fd.types";

const fd = createClient<paths>({
  baseUrl: "https://client-api.stg.future-demand.com/api/v3",
  headers: {
    Authorization:            `Bearer ${ACCESS_TOKEN}`,
    "X-Preferred-Partner-Id": PARTNER_ID,
  },
});

const { data, error } = await fd.GET("/events/", {
  params: { query: { limit: 5, descending: false } },
});
You get autocomplete and type-checking on every path, query param, body, and response — for free, kept in sync with the OpenAPI spec.

Option B — openapi-typescript-codegen (full generated client)

If you prefer a generated EventsService.list(...) style:
npm i -D openapi-typescript-codegen
npx openapi --input api-reference/openapi.json --output src/fd-client --client fetch
Then:
import { OpenAPI, EventsService } from "./fd-client";

OpenAPI.BASE = "https://client-api.stg.future-demand.com/api/v3";
OpenAPI.HEADERS = {
  Authorization:            `Bearer ${ACCESS_TOKEN}`,
  "X-Preferred-Partner-Id": PARTNER_ID,
};

const events = await EventsService.list({ limit: 5 });

Wrapping for auth + partner header + retries

Either option benefits from a wrapper that handles token refresh, partner selection, and 429 backoff. Here’s a reference:
import createClient, { type Middleware } from "openapi-fetch";
import type { paths } from "./fd.types";

const BASE = process.env.FD_BASE_URL!;

// Single-flight refresh logic — see /cookbook/token-refresh
let refreshPromise: Promise<Session> | null = null;
async function getSession(): Promise<Session> {
  const s = currentSession();
  if (s.expiresAt - Date.now() > 5 * 60 * 1000) return s;
  refreshPromise ??= doRefresh(s.refreshToken)
    .finally(() => { refreshPromise = null; });
  return refreshPromise;
}

const authMiddleware: Middleware = {
  async onRequest({ request }) {
    const { accessToken, partnerId } = await getSession();
    request.headers.set("Authorization", `Bearer ${accessToken}`);
    request.headers.set("X-Preferred-Partner-Id", partnerId);
    return request;
  },

  async onResponse({ response }) {
    if (response.status === 429) {
      const wait = Number(response.headers.get("Retry-After")) || 5;
      await new Promise(r => setTimeout(r, wait * 1000));
      // Caller should re-issue; alternatively wrap retry here.
    }
    return response;
  },
};

export const fd = createClient<paths>({ baseUrl: BASE });
fd.use(authMiddleware);
For full retry-with-backoff inside the middleware, see Rate limits.

Working with the CR API

For the CR-API host (see Two-host architecture), generate a second types file from a CR OpenAPI spec — ask Future Demand for the canonical document. Wire it up as a second openapi-fetch client with baseUrl: CR_BASE.
export const fd   = createClient<MainPaths>({ baseUrl: MAIN_BASE });
export const fdCr = createClient<CrPaths>({ baseUrl: CR_BASE });
fd.use(authMiddleware);
fdCr.use(authMiddleware);

Re-generation in CI

# .github/workflows/regen-fd-types.yml
name: Refresh FD types
on:
  schedule: [{ cron: "0 6 * * *" }]
  workflow_dispatch:

jobs:
  regen:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v5
        with: { node-version: 20 }
      - run: npm ci
      - run: npx openapi-typescript https://docs.future-demand.com/api-reference/openapi.json -o src/fd.types.ts
      - uses: peter-evans/create-pull-request@v6
        with:
          commit-message: "chore: refresh FD API types"
          title:          "chore: refresh FD API types"
          branch:         "auto/refresh-fd-types"
Run nightly. If types change, a PR appears; reviewing it tells you what moved on the backend before your integration breaks.

Caveats

Most modern generators handle both. openapi-typescript does. openapi-typescript-codegen does. If your generator complains, run a swagger → openapi conversion first (npx swagger2openapi).
Hit by the reference webapp but often absent from the published spec:
  • /events/{eid}/client_campaign_status[/task]
  • /events/{eid}/campaign_optimisation_status
  • /events/{eid}/ad_campaign_optimisation_status
  • /events/{eid}/attribution_model
  • CR API: /customers/*, events/bulk_upload, /events/last_updated_on_fe/{eid}, PUT /events/{eid} (domain save)
Your generated client won’t include these — write thin manual wrappers, and ask Future Demand to add them to the spec. The CR API is a separate host and needs its own OpenAPI document (or manual wrappers); it is not in the main spec.
POST /campaigns/, PUT /events/{eid}/client_campaign_status?..., POST /events/{eid}/series?event_series=A&event_series=B, etc. Generated clients handle these correctly, but if you hand-build a wrapper, watch out for the convention.
It’s actually Authorization: Bearer <token> — see Authentication. Configure your generated client accordingly.