Skip to main content
Tokens are valid for ~1 hour. Fetching a new token on every request adds unnecessary latency and risks hitting rate limits. Production integrations should:
  1. Cache the access token in memory along with its expires_in value
  2. Refresh proactively when within 60 seconds of expiry
  3. On 401, invalidate the cache and fetch a new token, then retry the request once
The complete wrappers below handle all three cases and include gzip compression.
import { gzipSync } from 'zlib';

let _cachedToken = null;
let _tokenExpiresAt = 0;

async function getAccessToken() {
  const now = Math.floor(Date.now() / 1000);
  if (_cachedToken && now < _tokenExpiresAt - 60) return _cachedToken;

  const res = await fetch(TOKEN_URL, {
    method: "POST",
    headers: {
      "Authorization": "Basic " + Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString("base64"),
      "Content-Type": "application/x-www-form-urlencoded",
    },
    body: "grant_type=client_credentials&scope=billreview%3Awrite",
  });
  if (!res.ok) throw new Error(`Token request failed: ${res.status}`);
  const data = await res.json();
  _cachedToken = data.access_token;
  _tokenExpiresAt = Math.floor(Date.now() / 1000) + (data.expires_in ?? 3600);
  return _cachedToken;
}

async function reviewBill(billPayload) {
  const compressed = gzipSync(Buffer.from(JSON.stringify(billPayload)));

  const post = async (token) => fetch(`${API_ENDPOINT}/v1/review`, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${token}`,
      "x-api-key": API_KEY,
      "Content-Type": "application/json",
      "Content-Encoding": "gzip",
    },
    body: compressed,
  });

  let res = await post(await getAccessToken());
  if (res.status === 401) {
    _cachedToken = null;                    // invalidate and retry once
    res = await post(await getAccessToken());
  }
  if (!res.ok) throw new Error(`Review failed: ${res.status}`);
  return res.json();
}