Partner = tenant

A partner is one customer of Future Demand — one organisation, one billing relationship, one isolated dataset. Every authenticated API call is scoped to a partner via the X-Preferred-Partner-Id header.
X-Preferred-Partner-Id: partner-id-acme
This header is always required when the user belongs to more than one partner, and is strongly recommended to send always (so single-partner code keeps working if you later onboard multi-partner users).

How to discover the user’s partners

After login, decode the IdToken and read cognito:groups. The groups that match /(partner-id-.+|fd\d+)/ are partners the user belongs to:
import { jwtDecode } from "jwt-decode";

const claims = jwtDecode<{ "cognito:groups": string[] }>(idToken);
const partnerIds = claims["cognito:groups"]
  .filter(g => /^(partner-id-.+|fd\d+)$/.test(g));

// e.g. ["partner-id-acme", "partner-id-beta"]
If the user has one partner, set it on every request. If multiple, your UI should let the user pick (the reference webapp shows a PartnerSelect after login). Persist the choice client-side and re-send on every reload.

Fetching the partner record

Once you’ve picked a partner, the canonical “who am I scoped to” call is:
curl -s "$BASE/partners/" \
  -H "Authorization: Bearer $FD_ACCESS_TOKEN" \
  -H "X-Preferred-Partner-Id: $FD_PARTNER_ID"
Response (abbreviated):
{
  "id":            "partner-id-acme",
  "name":          "Acme Concerts",
  "domain":        "acme.com",
  "currency":      "EUR",
  "vertical":      "live_entertainment",
  "demand_prediction":      true,
  "campaign_recommendations": true,
  "status":        "active",
  "pricing_tier":  "growth",
  "campaign_pricing_model": "...",
  "ner_active":    false
}
The boolean flags drive which products are available. Two common ones:
FlagMeaning
demand_predictionLookout is enabled for this partner.
campaign_recommendationsWave is enabled for this partner.
Gate your UI on those.

Permissions and features

Two further endpoints, both partner-scoped:
# Per-user permission strings for the currently selected partner
curl -s "$BASE/permissions" \
  -H "Authorization: Bearer $FD_ACCESS_TOKEN" \
  -H "X-Preferred-Partner-Id: $FD_PARTNER_ID"

# Per-partner feature flags
curl -s "$BASE/features" \
  -H "Authorization: Bearer $FD_ACCESS_TOKEN" \
  -H "X-Preferred-Partner-Id: $FD_PARTNER_ID"
The reference webapp gates clickability on useHasPermission(...) for things like Wave access (Permissions.wave) and Package Builder (Permissions.backhaul). Mirror that.
Re-fetch both on partner switch. If a multi-partner user changes their selected partner, the previous permissions / features arrays are no longer valid.

Why client_id shows up sometimes

The backend uses two names for the same concept in different tables:
  • partner_id — the public, stable identifier (partner-id-acme, fd123). Use this everywhere you can.
  • client_id — an internal numeric/string id returned by GET /partners/ as result.data.id. A few legacy endpoints still expect it.
The reference webapp stores both in localStorage.partnerDetails and uses whichever the calling endpoint demands. For new integrations, treat partner_id as canonical — every endpoint documented in the API Reference accepts it. If you hit a call that requires client_id explicitly, read it from the /partners/ response.

Multi-partner users in practice

type PartnerCtx = { partnerId: string; partnerName: string };

function setPartner(p: PartnerCtx) {
  localStorage.setItem("fd:partner", JSON.stringify(p));
  apiClient.defaults.headers["X-Preferred-Partner-Id"] = p.partnerId;

  // Re-fetch partner-scoped data
  refetchPermissions();
  refetchFeatures();
  invalidateAllPartnerScopedQueries();
}
If you use React Query or SWR, use the partner id as part of every query key so cached data is partitioned per partner. That removes a whole class of “cross-partner data leak” bugs from your UI.

Admin partners and the fd-admin group

Future Demand staff accounts carry the fd-admin group and can act as any partner. The endpoints under partner_admin (POST /partners/, PUT /partners/, /auth/users/csv_export, PUT /rest_demo_data/...) are admin-only and not intended for partner integrations. They are documented in the API Reference for completeness, but should never appear in your UI.