Automates the monthly check that Sniffspot’s payout matches what they actually owe. Replaces a manual click-through of ~50 reservations (three clicks each) with a single query against the page’s own data layer, producing a Quicken-ready spreadsheet and flagging any booking charged off-rate.
Built 2026-08-06 after a July reconciliation turned up two things a manual tally misses: refunded bookings that look identical to paid ones in the list view, and membership discounts that shift the commission base.
Sniffspot has under-paid before (an $8 discrepancy that took two months to recover). There is no monthly export in the host UI. The earnings breakdown is one click deep per reservation, so verifying a 50-booking month by hand is slow enough that most hosts almost certainly don’t bother.
Booking price:
(12 + 6 x (dogs - 1)) x hours
$12 for the first dog, $6 for each additional dog, for every hour — not just the first. This was the single biggest source of error in the manual estimates: a 2-dog / 2-hour booking is $36, not $42.
Deductions, in order:
| Component | Rule |
|---|---|
| Membership discount | 0 / 5% / 10% of booking total, by tier |
| Sniffspot commission | exactly 22.00% of the post-discount amount |
| Payment processing | 0.00%–3.61%, varies by guest payment method |
netToHost = amountDue - sniffpassHostDiscount - sniffspotFee - stripeFee
The 22% held on all 46 non-refunded July bookings with zero deviation — which is what makes it a usable audit invariant. Processing fees are erratic and frequently $0.00; that variance is guest payment method and is not host-controllable, so don’t chase it.
Membership tiers observed: none, basic (0%), bronze (5%), silver
(10%). No gold seen as of July 2026. Any other value should be treated as new
and investigated.
The host reservations page is a React-on-Rails app with an Apollo GraphQL client
exposed at window.__APOLLO_CLIENT__. Every Reservation object already carries
the full fee breakdown, so there is no need to open individual reservations.
Direct POSTs to /graphql are rejected (returns
{"error":{"message":"something wrong"}}) even with the csrf-token meta tag
and credentials: 'same-origin'. Some other guard is in play. Go through the
Apollo client instead — reuse the query document the page already built:
const c = window.__APOLLO_CLIENT__;
const oq = [...c.getObservableQueries('all').values()].find(q =>
q.query?.definitions[0].name?.value === 'reservationsPaged' &&
q.variables?.status?.[0] === 'past');
const r = await c.query({
query: oq.query,
variables: { page:1, limit:200, sortBy:'DATE', sortOrder:'DESC',
asHost:true, status:['past'] },
fetchPolicy: 'network-only'
});
window.__RES = r.data.reservationsPaged.collection;
limit: 200 reaches back roughly 12 months at current booking volume. The
“Past” tab must be clicked first so the query document exists to borrow.
Fields on each Reservation:
id, datePart, date, time, length, quantity, price, amountDue,
sniffpassType, sniffpassHostDiscount, sniffpassDiscount,
sniffspotFee, stripeFee, netToHost, hostRefund, status,
user { firstname, lastInitial }
Note price is the per-hour base rate, not the booking total — use
amountDue.
Refunded bookings are visually identical to paid ones in the list view. They only reveal themselves in the per-reservation breakdown, which is exactly what nobody opens.
Detection: hostRefund === true, or netToHost < 1.
July 2026 had three (Rachel L. 7/1, Kelli A. 7/8, Rachel L. 7/24) totalling $48 of gross that returned $1.65. A manual tally counting them at face value would show a $48 shortfall and send you chasing a Sniffspot error that doesn’t exist.
Always exclude refunds from totals and list them separately.
Task name: Sniffspot fee reconciliation · Every Monday, ~12:00 PM · Runs on this computer.
There is no monthly cadence option (Manual / Hourly / Daily / Weekdays / Weekly only), so month selection lives inside the task prompt rather than in the schedule:
Walkthrough: Mon Aug 31 → August PRELIM. Mon Sep 7 → August FINAL, September excluded. Mon Sep 14/21/28 → September PRELIM. Mon Oct 5 → September FINAL. Every run covers exactly one month; two months never blend.
Output filenames encode month and status (sniffspot_2026_08_FINAL.xlsx,
sniffspot_2026_09_PRELIM.xlsx) so a preliminary run can never overwrite a
closeout already reconciled and filed.
The task stops and reports if it finds a logged-out state rather than producing a partial month. A missed week degrades to a notification, not a wrong number.
The task needs Chrome running and logged in. Two pieces:
Global Chrome setting is “Delete data sites have saved to your device when you
close all windows” — good hygiene, but it wipes _sniffspot_session on close.
Carve out one exception at chrome://settings/content/siteData →
Allowed to save data on your device → Add:
[*.]sniffspot.com
This is not the same list as the third-party-cookie allowlist under Privacy → Third-party cookies. Adding it there does nothing for clear-on-exit; that was the first attempt and it failed. Everything else still gets wiped on close.
Session cookie expiry runs ~30 days out (_sniffspot_session, checked
2026-08-06, expires 2026-09-05). Verify anytime in DevTools → Application →
Cookies → https://www.sniffspot.com.
Chrome → Settings → On startup → Open a specific page or set of pages:
https://www.sniffspot.com/host_account/reservation?status%5B0%5D=upcoming
Ensures the Apollo client is initialized whenever Chrome comes up.
Opens Chrome five minutes before the task fires, only if it isn’t already running.
/home/aztechguy/Projects/sniffspot/sniffspot-chrome.sh (mode 755)~/.local/state/sniffspot-chrome.log#Sniffspot weekly automation of gather financial data in chrome
55 11 * * 1 /home/aztechguy/Projects/sniffspot/sniffspot-chrome.sh
#!/usr/bin/env bash
set -uo pipefail
URL="https://www.sniffspot.com/host_account/reservation"
LOG="$HOME/.local/state/sniffspot-chrome.log"
mkdir -p "$(dirname "$LOG")"
log(){ printf '%s %s\n' "$(date '+%F %T')" "$*" >>"$LOG"; }
# Already running? Leave it alone.
if pgrep -x chrome >/dev/null 2>&1; then
log "chrome already running — no action"
exit 0
fi
# cron has no desktop context; lift it from gnome-shell
SHELL_PID=$(pgrep -u "$(id -un)" -x gnome-shell | head -1)
if [ -z "$SHELL_PID" ]; then
log "no desktop session — skipping"
exit 0
fi
while IFS= read -r -d '' kv; do
case "$kv" in
DISPLAY=*|WAYLAND_DISPLAY=*|XAUTHORITY=*|\
DBUS_SESSION_BUS_ADDRESS=*|XDG_RUNTIME_DIR=*) export "$kv" ;;
esac
done < "/proc/$SHELL_PID/environ"
setsid /usr/bin/google-chrome --new-window "$URL" >/dev/null 2>&1 &
log "launched chrome"
$USER is not set under cron on Ubuntu. The original script used
pgrep -u "$USER", which became pgrep -u "" and matched nothing — logging
no desktop session — skipping even though GNOME was clearly running. Use
$(id -un), which asks the system rather than trusting the environment. Failure
mode is silent and the message actively points the wrong direction.
Redirect Chrome’s stderr to /dev/null, not the log. Chrome emits hundreds
of benign Failed to create API on Chrome object and DEPRECATED_ENDPOINT
lines per launch, which bury the script’s own entries. Both messages are noise
and can be ignored. With >/dev/null 2>&1 the log becomes the audit trail it
should be:
2026-08-06 10:55:06 launched chrome
2026-08-10 11:55:01 chrome already running — no action
pgrep -x chrome, not pgrep -f chrome. -x matches the process name
exactly. -f would match the grep itself, any editor with “chrome” in a path,
etc. Note chrome_crashpad_handler lingers after Chrome exits but does not
match -x chrome, so it won’t produce a false “already running”.
Running the script from a terminal proves nothing — an interactive shell already
has DISPLAY and DBUS_SESSION_BUS_ADDRESS. The /proc/PID/environ lifting is
only exercised under cron. Close Chrome, set a throwaway line a few minutes out,
and confirm launched chrome appears.
Four lines, and the membership discount appears exactly once:
| Line | July 2026 | Category |
|---|---|---|
| Sniffspot bookings (billable gross) | $852.00 | Other Income |
| Membership discounts | −$38.70 | Discounts (Business) |
| Sniffspot commission | −$178.93 | Commissions & Fees |
| Payment processing | −$16.48 | Merchant Fees |
| Balance due | $617.89 |
Gross is $900.00 across 49 reservations; $48.00 of that was refunded, leaving $852.00 billable.
The trap: adding the discount as an expense line without removing the existing revenue-deduction line subtracts it twice, producing $579.19 against an actual deposit of $617.90 — a $38.70 phantom shortfall. Either treatment is defensible (IRS Pub 334 sanctions both reducing sales and expensing the discount); pick one, stay consistent year to year, and never both.
Standing check: the invoice balance must equal the Stripe deposit into Amex business checking. If it doesn’t, a line is duplicated or missing — far faster than re-deriving the month.
sniffspotFee / (amountDue - sniffpassHostDiscount) ≠ 0.2200 is a real finding.
Flag it with date and guest name.gsettings get org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type
— anything other than 'nothing' may skip a week.[*.]google.com
may also need the on-device-data exception. Not needed as long as
_sniffspot_session persists.