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

Hi, it’s a known problem and we are in progress of testing and deploying the fix for it

Thanks, please let me know when it gets deployed

Fixes deployed to production servers

Thank you!