CORS policy

The Future Demand API permits cross-origin requests only on the unauthenticated GET /status/ endpoint. Every authenticated endpoint rejects browser CORS. This is deliberate: credentials must not leak to a browser. Authenticated calls must go through your own backend (or use short-lived access tokens served from your origin).

The proxy pattern

Browser (your-app.com)
   │  /api/...

Your backend (your-app.com/api)
   │  Authorization + X-Preferred-Partner-Id

Future Demand API (client-api.prd.future-demand.com/api/v3)
Your backend:
  1. Holds the long-lived refresh token (or service-account credentials).
  2. Manages session refresh.
  3. Strips browser cookies/headers and forwards only what’s needed.
  4. Adds Authorization: Bearer <access_token> and X-Preferred-Partner-Id: <partner_id> per the calling user’s selected partner.
Minimal Express example (see also Your First Integration):
import express from "express";
import fetch from "node-fetch";

const app = express();
const BASE = process.env.FD_BASE_URL!;          // https://client-api.stg.future-demand.com/api/v3

app.use(express.json());
app.use(express.raw({ type: "*/*", limit: "100mb" }));

app.use("/api/*", async (req, res) => {
  const session = await sessionFor(req);         // your auth layer
  if (!session) return res.status(401).end();

  const url = `${BASE}${req.params[0]}${req.url.includes("?") ? "?" + req.url.split("?")[1] : ""}`;
  const upstream = await fetch(url, {
    method:  req.method,
    headers: {
      Authorization:           `Bearer ${session.accessToken}`,
      "X-Preferred-Partner-Id": session.partnerId,
      "Content-Type":           req.headers["content-type"] ?? "application/json",
    },
    body: ["GET", "HEAD"].includes(req.method!) ? undefined : req.body,
  });

  res.status(upstream.status);
  upstream.headers.forEach((v, k) => res.setHeader(k, v));
  upstream.body?.pipe(res);
});

app.listen(8787);

Two-host architecture

The reference webapp talks to two Future Demand hosts:
HostBase URLPurposeUsed by
Main APIhttps://client-api.prd.future-demand.com/api/v3The bulk of endpoints — events, recommendations, campaigns, packages, …Almost every guide.
CR API (Customer Representation)ask your account contact — separate host, distinct from client-api.*Event ingest, simulation, bulk upload, the second step of status-update.Event Editor, Bulk Upload, Simulation, the cross-host status update on Event Detail.
// Two axios instances, or two fetch helpers
export const fd   = createClient({ baseURL: API_URL + "/v3" });
export const fdCr = createClient({ baseURL: CR_URL });

// Same headers on both
fd.interceptors.request.use(attachAuthAndPartner);
fdCr.interceptors.request.use(attachAuthAndPartner);
The CR API does not use the /v3 prefix. Send /events/{eid}, not /v3/events/{eid}. Confusing the two is the most common host-mismatch error.

Which calls go to which host

Calls that go to the CR API (from the reference webapp):
  • GET /events/{eid} — only when loading raw event payload for the Event Editor (includes lineups_raw, description).
  • PUT /events/{eid}?event_type=... — Event Editor save.
  • POST /events/?event_type=... — Simulation create.
  • POST events/bulk_upload?event_type=... — Bulk event upload (note: no leading slash on this path in the reference webapp).
  • PUT /events/last_updated_on_fe/{eid} — second half of every setFrontendStatus (flag / archive).
Everything else goes to the main API.

POST /media — the other path quirk

POST /media (creative upload) goes to the main API host but without the /v3 prefix. The reference webapp uses a third bare axios instance for this single endpoint. Replicate or you get a 404.

Token-bound sessions for embedded widgets

The reference webapp also supports two link-based session modes (used for embedded widgets, not the main webapp flow):
  • X-Partner-Token: <token> — read from sessionStorage["partner-session"]. Used by partner-link flows.
  • X-Auth-Code: <token> — read from sessionStorage["anonymous-session"]. Used by anonymous embed flows.
Partners building first-party server-to-server integrations should ignore both and stick with Authorization: Bearer <token>.

Per-user vs server-to-server proxying

ModeRefresh tokenPer-user audit
Server-to-serverStored in your backend secret store. One service account acts for all users.No per-user audit on Future Demand’s side.
Per-userOne refresh token per user, server-managed via HttpOnly cookie.Yes.
Per-user is more work but gives you cleaner audit logs and matches how Future Demand expects multi-tenant SaaS to integrate. Recommend it for production.

Common mistakes

Will fail at CORS for every authenticated endpoint. Proxy through your backend.
Easy to do if you have one global axios instance. Use two distinct instances and make the second one obvious in your code.
Main needs /v3, CR doesn’t. The POST /media exception adds further confusion — keep the special case isolated.
Use HttpOnly cookies or keep refresh entirely server-side. The browser should only see the short-lived access token (or nothing, if you proxy fully).