Conventions

ContextFormat
Timestamps in JSON payloads (reads)ISO 8601 with offset or Z. Example: "2026-06-12T18:30:00Z".
Date-only query params (since, until, start_date, end_date, email_sent_date, …)YYYY-MM-DD. No time component.
Token expiry (ExpiresIn)Integer seconds from now.
DisplayUse the partner’s profile.zoneinfo (e.g. Europe/Berlin). The API does not normalise to UTC for display fields.

Sending date params

Always validate before sending. The reference webapp uses moment to ensure YYYY-MM-DD:
import { isValid, format, parseISO } from "date-fns";

function toApiDate(d: Date | string): string {
  const date = typeof d === "string" ? parseISO(d) : d;
  if (!isValid(date)) throw new Error(`Invalid date: ${d}`);
  return format(date, "yyyy-MM-dd");
}

// Usage
const params = {
  since: toApiDate("2026-05-01"),
  until: toApiDate(new Date()),
};
Don’t send full ISO timestamps where a date-only is expected — some endpoints silently drop the time portion, others 400.

Parsing timestamps from responses

import { parseISO } from "date-fns";

const startsAt = parseISO(event.start_date_time);   // "2026-06-12T18:30:00Z" → Date
If you display in the user’s local zone, format using their browser locale. If you display in the partner’s zone, fetch GET /profile/ and use profile.zoneinfo:
const fmt = new Intl.DateTimeFormat("de-DE", {
  timeZone: profile.zoneinfo ?? "UTC",
  dateStyle: "medium",
  timeStyle: "short",
});
fmt.format(startsAt);

DST and partner zoneinfo

profile.zoneinfo is an IANA TZ identifier (Europe/Berlin, America/New_York). Modern Intl.DateTimeFormat and date-fns/tzdata handle DST correctly. The field is set on user provisioning; partners can update it via PUT /profile/ { zoneinfo: "..." }.

Token expiry — ExpiresIn

// At sign-in time:
const expiresAt = Date.now() + (response.ExpiresIn ?? 3600) * 1000;

// At call time:
if (expiresAt - Date.now() < 5 * 60 * 1000) await refresh();
ExpiresIn is seconds, not milliseconds. Easy to confuse — multiply by 1000 when constructing Date or epoch ms.

Filter date ranges

A common UX is “last 14 days”, “last 8 weeks”, “all”:
function dateRange(preset: "14d" | "8w" | "all") {
  const today = new Date();
  switch (preset) {
    case "14d": return { since: subDays(today, 14),    until: today };
    case "8w":  return { since: subWeeks(today, 8),    until: today };
    case "all": return { since: new Date("1900-01-01"), until: new Date("2100-01-01") };
  }
}

// Don't forget to format
const { since, until } = dateRange(preset);
fetchEvents({ since: format(since, "yyyy-MM-dd"), until: format(until, "yyyy-MM-dd") });
since: "1900-01-01" and until: "2100-01-01" is the canonical “all time” range — the reference webapp uses exactly these on /daily_revenue_summary/.

Common mistakes

new Date().toISOString().slice(0,10) returns UTC date, which may be yesterday for users in the Americas. Compute in the user’s zone, or use the partner zoneinfo, depending on UX.
2026-06-12T18:30:00 (no offset, no Z) is ambiguous. Future Demand sends offsets; if you ever strip them, do it at the display layer only.
Yields expiresAt = ~1 hour ago. Token “always expired”, refresh loop. Easy mistake; double-check the unit.
A concert in Munich on 2026-06-12T18:30:00Z is actually 20:30 local. For event-time displays, use event.tz (when present) or profile.zoneinfo, not the user’s browser zone.