getFirstWorkingDay not reporting expected date (today)

getFirstWorkingDay is returning 1 day beyond the actual first working day in my case. For instance I queried for today, which had lots of slots available, and it returned tomorrow. Can you guys look into this?

I’m attaching a self contained test. This is the output:

============================================================
 SimplyBook.me getFirstWorkingDay vs getStartTimeMatrix repro
============================================================
API URL        : https://user-api.simplybook.me
Company login  : arawanmassage
Service (event): 39
Provider (unit): any
Shop timezone  : America/Los_Angeles
Host now (UTC) : 2026-06-05T16:57:07.742Z
"Today" in TZ  : 2026-06-05

--- getFirstWorkingDay ----------------------------------
request :  [{"unit_group_id":null,"event_id":39}]
response:  "2026-06-06"

--- getStartTimeMatrix 2026-06-05 ------------------------
request :  ["2026-06-05","2026-06-05",39,null,1]
response: 17 slot(s) -> first: 15:00:00, 15:15:00, 15:30:00, 15:45:00, 16:00:00, 16:15:00, 16:30:00, 16:45:00

--- getStartTimeMatrix 2026-06-06 ------------------------
request :  ["2026-06-06","2026-06-06",39,null,1]
response: 37 slot(s) -> first: 10:00:00, 10:15:00, 10:30:00, 10:45:00, 11:00:00, 11:15:00, 11:30:00, 11:45:00

============================================================
getFirstWorkingDay      = 2026-06-06
getStartTimeMatrix(2026-06-05) = 17 slot(s) (earliest 15:00:00)

✗ BUG REPRODUCED: getFirstWorkingDay says the first bookable day is 2026-06-06,
  but 2026-06-05 (earlier) already has 17 bookable slot(s) per getStartTimeMatrix.
============================================================

the test script

#!/usr/bin/env node
/**
 * Self-contained reproduction for a SimplyBook.me bug:
 *
 *   getFirstWorkingDay() returns a LATER date than the first date that
 *   actually has bookable slots according to getStartTimeMatrix().
 *
 * In other words SBM tells the booking widget "the first day you can book is
 * <tomorrow>", yet getStartTimeMatrix() for <today> returns a non-empty list of
 * start times. The booking page therefore defaults to the wrong (later) date.
 *
 * This script depends on nothing but Node 18+ (built-in global `fetch`). It can
 * be handed to SimplyBook.me support to reproduce, since it prints the exact
 * JSON-RPC requests and responses.
 *
 * ---------------------------------------------------------------------------
 * Usage:
 *   export SBM_API_LOGIN=your_company_login
 *   export SBM_API_KEY=your_public_api_key
 *   node sbm_first_working_day_bug.mjs --service 39 [--provider 32] \
 *        [--date 2026-06-05] [--tz America/Los_Angeles] [--api-url https://user-api.simplybook.me]
 *
 * Credentials may also be passed as --login / --key instead of env vars.
 *
 * Exit codes: 0 = no discrepancy, 2 = BUG reproduced, 1 = usage/transport error.
 * ---------------------------------------------------------------------------
 */

// ---- tiny arg parser ----
const args = {};
for (let i = 2; i < process.argv.length; i++) {
  const a = process.argv[i];
  if (a.startsWith('--')) args[a.slice(2)] = process.argv[i + 1]?.startsWith('--') ? true : process.argv[++i];
}

const API_URL  = args['api-url'] || process.env.SBM_API_URL || 'https://user-api.simplybook.me';
const LOGIN    = args.login || process.env.SBM_API_LOGIN;
const API_KEY  = args.key   || process.env.SBM_API_KEY;
const SERVICE  = args.service != null ? Number(args.service) : null;          // event_id (required)
const PROVIDER = args.provider != null ? Number(args.provider) : null;        // unit_group_id (optional; null = any)
const TZ       = args.tz || 'America/Los_Angeles';                            // shop timezone for "today"
const DATE     = args.date || todayInTz(TZ);                                  // the date we expect to be bookable

if (!LOGIN || !API_KEY) {
  console.error('ERROR: set SBM_API_LOGIN and SBM_API_KEY (or pass --login / --key).');
  process.exit(1);
}
if (SERVICE == null || Number.isNaN(SERVICE)) {
  console.error('ERROR: --service <eventId> is required.');
  process.exit(1);
}

// ---- minimal JSON-RPC over fetch ----
let rpcId = 1;
async function rpc(url, method, params, headers = {}) {
  const body = { jsonrpc: '2.0', method, params, id: rpcId++ };
  const res = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', ...headers },
    body: JSON.stringify(body),
  });
  const text = await res.text();
  let json;
  try { json = JSON.parse(text); } catch { throw new Error(`Non-JSON response from ${method}: HTTP ${res.status} ${text.slice(0, 300)}`); }
  return { req: body, res: json };
}

function todayInTz(tz) {
  const p = new Intl.DateTimeFormat('en-US', { timeZone: tz, year: 'numeric', month: '2-digit', day: '2-digit' })
    .formatToParts(new Date());
  const get = (t) => p.find((x) => x.type === t).value;
  return `${get('year')}-${get('month')}-${get('day')}`;
}

function nextDay(isoDate) {
  const d = new Date(isoDate + 'T00:00:00Z');
  d.setUTCDate(d.getUTCDate() + 1);
  return d.toISOString().slice(0, 10);
}

function slotCount(matrix, date) {
  const s = matrix && matrix[date];
  return Array.isArray(s) ? s : [];
}

async function main() {
  console.log('============================================================');
  console.log(' SimplyBook.me getFirstWorkingDay vs getStartTimeMatrix repro');
  console.log('============================================================');
  console.log('API URL        :', API_URL);
  console.log('Company login  :', LOGIN);
  console.log('Service (event):', SERVICE);
  console.log('Provider (unit):', PROVIDER == null ? 'any' : PROVIDER);
  console.log('Shop timezone  :', TZ);
  console.log('Host now (UTC) :', new Date().toISOString());
  console.log('"Today" in TZ  :', DATE);
  console.log('');

  // 1) Authenticate (public API): getToken(companyLogin, apiKey) at /login
  const tok = await rpc(`${API_URL}/login`, 'getToken', [LOGIN, API_KEY]);
  const token = tok.res.result;
  if (!token) throw new Error('getToken failed: ' + JSON.stringify(tok.res));
  const authHeaders = { 'X-Company-Login': LOGIN, 'X-Token': token };

  // 2) getFirstWorkingDay({ unit_group_id, event_id })
  const fwdCall = await rpc(API_URL, 'getFirstWorkingDay', [{ unit_group_id: PROVIDER, event_id: SERVICE }], authHeaders);
  const firstWorkingDay = fwdCall.res.result;
  console.log('--- getFirstWorkingDay ----------------------------------');
  console.log('request : ', JSON.stringify(fwdCall.req.params));
  console.log('response: ', JSON.stringify(fwdCall.res.result ?? fwdCall.res));
  console.log('');

  if (!firstWorkingDay) throw new Error('getFirstWorkingDay returned no date: ' + JSON.stringify(fwdCall.res));

  // 3) getStartTimeMatrix(dateFrom, dateTo, eventId, unitId, count) for the dates of interest
  const datesToProbe = [...new Set([DATE, nextDay(DATE), firstWorkingDay])].sort();
  const counts = {};
  for (const d of datesToProbe) {
    const m = await rpc(API_URL, 'getStartTimeMatrix', [d, d, SERVICE, PROVIDER, 1], authHeaders);
    const slots = slotCount(m.res.result, d);
    counts[d] = slots;
    console.log(`--- getStartTimeMatrix ${d} ------------------------`);
    console.log('request : ', JSON.stringify(m.req.params));
    console.log(`response: ${slots.length} slot(s)` + (slots.length ? ` -> first: ${slots.slice(0, 8).join(', ')}` : ''));
    console.log('');
  }

  // 4) Verdict
  const todaySlots = counts[DATE] || [];
  const bug = DATE < firstWorkingDay && todaySlots.length > 0;
  console.log('============================================================');
  console.log(`getFirstWorkingDay      = ${firstWorkingDay}`);
  console.log(`getStartTimeMatrix(${DATE}) = ${todaySlots.length} slot(s)` + (todaySlots.length ? ` (earliest ${todaySlots[0]})` : ''));
  if (bug) {
    console.log('');
    console.log(`✗ BUG REPRODUCED: getFirstWorkingDay says the first bookable day is ${firstWorkingDay},`);
    console.log(`  but ${DATE} (earlier) already has ${todaySlots.length} bookable slot(s) per getStartTimeMatrix.`);
    console.log('============================================================');
    process.exit(2);
  }
  console.log('');
  console.log('✓ No discrepancy: getFirstWorkingDay is consistent with getStartTimeMatrix for the probed dates.');
  console.log('============================================================');
  process.exit(0);
}

main().catch((err) => { console.error('\nERROR:', err.message || err); process.exit(1); });

Invoked as:

SBM_API_LOGIN=“arawanmassage” SBM_API_KEY=“<our_key>” ./sbm_first_working_day_bug.mjs --service 39

Thanks
Miguel

hi, what it the minimum time till booking setting for your account?

It is 10 minutes, our calendar granularity is 15 minutes