You can generate a typed TypeScript client directly from 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).
/events/{eid}/client_campaign_status[/task], /events/{eid}/campaign_optimisation_status, and a handful of CR API routes are hit by the reference webapp but absent from the canonical OpenAPI spec. Your generated client won’t include them — write thin manual wrappers, and lobby Future Demand to add them to the spec.
POST /campaigns/, PUT /events/{eid}/client_campaign_status?..., POST /package_builder/packages/merge?packages_ids=A&packages_ids=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.