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