API key

const base = "https://staging.api.hofj.com";
const apiKey = process.env.HOJ_API_KEY; // or import.meta.env in Vite

const res = await fetch(`${base}/v1/products?limit=10&brand=Weebora&locale=it`, {
  headers: {
    Authorization: `Bearer ${apiKey}`,
    Accept: "application/json",
  },
});

if (!res.ok) {
  const problem = await res.json();
  throw new Error(problem.detail ?? res.statusText);
}

const body = await res.json();
console.log(body.data);

Product detail

Use the product detail endpoint to build product pages. The response includes pricing, availability, date ranges, duration, product image / gallery, and related category / venue / destination summaries.
const detail = await fetch(`${base}/v1/products/1127?brand=Weebora&locale=it`, {
  headers: {
    Authorization: `Bearer ${apiKey}`,
    Accept: "application/json",
  },
});

if (!detail.ok) {
  const problem = await detail.json();
  throw new Error(problem.detail ?? detail.statusText);
}

const product = await detail.json();
console.log(product.data);
Internal clients can request the extended CMS payload with extended=true. That mode fetches the product upstream with populate=*, so rawAttributes includes all fields exposed by the CMS for that product. External clients receive 403 Forbidden when using that parameter.

OAuth2 (server-side only)

Never expose client_secret in a browser bundle. Run this on your backend or in a secure worker.
async function getAccessToken(base, clientId, clientSecret) {
  const body = new URLSearchParams({
    grant_type: "client_credentials",
    client_id: clientId,
    client_secret: clientSecret,
  });
  const res = await fetch(`${base}/v1/oauth/token`, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body,
  });
  if (!res.ok) throw new Error(await res.text());
  const json = await res.json();
  return json.access_token;
}

Node.js notes

  • Use fetch (Node 18+) or undici.
  • For mTLS or corporate proxies, configure your HTTP agent accordingly.