Models¶
All response models are Pydantic v2 BaseModel
subclasses. The API returns camelCase field names; each model uses
alias_generator = to_camel so you can access fields with Pythonic
snake_case attributes.
customer = client.customers.get("12345")
print(customer.first_name) # snake_case attribute
print(customer.model_dump(by_alias=True)) # camelCase dict
Customers¶
CustomerListItem ¶
Bases: SonnysModel
Summary customer record returned by client.customers.list().
Contains identifiers, name, phone, and active status.
Customer ¶
Bases: SonnysModel
Full customer profile returned by client.customers.get(id).
Includes address, contact details, loyalty info, and SMS opt-in status.
Items¶
Item ¶
Bases: SonnysModel
A menu item (wash package, product, or service) returned by client.items.list().
Includes SKU, pricing, and department info.
Employees¶
EmployeeListItem ¶
Bases: SonnysModel
Summary employee record returned by client.employees.list().
Contains name and employee ID.
Employee ¶
Bases: SonnysModel
Full employee profile returned by client.employees.get(id).
Includes contact info, active status, and start date.
ClockEntry ¶
Bases: SonnysModel
A single clock-in/clock-out record for an employee.
Returned by client.employees.get_clock_entries().
Sites¶
Site ¶
Bases: SonnysModel
A car wash site/location returned by client.sites.list().
Contains the site code, name, and timezone.
Giftcards¶
GiftcardListItem ¶
Bases: SonnysModel
A giftcard liability record returned by client.giftcards.list().
Contains card number, value, amount used, and site code.
Washbooks¶
WashbookListItem ¶
Bases: SonnysModel
Summary washbook record returned by client.washbooks.list().
Contains ID, balance, status, and sign-up date.
Washbook ¶
Bases: SonnysModel
Full washbook detail returned by client.washbooks.get(id).
Includes customer, vehicles, tags, and recurring billing info.
WashbookTag ¶
Bases: SonnysModel
An RFID tag or barcode associated with a washbook account.
WashbookVehicle ¶
Bases: SonnysModel
A vehicle linked to a washbook account.
Recurring¶
RecurringListItem ¶
Bases: SonnysModel
Summary recurring account returned by client.recurring.list().
Contains ID, plan name, status, balance, and billing site.
Recurring ¶
Bases: SonnysModel
Full recurring account detail returned by client.recurring.get(id).
Includes customer, vehicles, tags, billing history, and status history.
RecurringStatusChange ¶
Bases: SonnysModel
A status change event returned by client.recurring.list_status_changes().
Records old/new status, date, employee, and site.
RecurringModification ¶
Bases: Recurring
A recurring account with its modification history.
Extends :class:Recurring with a modifications list.
Returned by client.recurring.list_modifications().
Transactions¶
TransactionListItem ¶
Bases: SonnysModel
Summary transaction record returned by client.transactions.list() and client.transactions.list_by_type().
Contains transaction number, ID, total, and date.
TransactionV2ListItem ¶
Bases: TransactionListItem
Enriched transaction summary returned by client.transactions.list_v2().
Extends :class:TransactionListItem with customer ID, recurring plan flags,
and transaction status.
Transaction ¶
Bases: SonnysModel
Full transaction detail returned by client.transactions.get(id).
Includes line items, tenders, discounts, customer/employee info, and prepaid/recurring flags.
TransactionJobItem ¶
Bases: Transaction
Transaction record returned by client.transactions.load_job().
Extends :class:Transaction with additional fields from the batch job
endpoint.
TransactionTender ¶
Bases: SonnysModel
A payment tender (cash, credit, etc.) within a transaction detail.
Contains tender type, amount, change, and optional credit card info.
TransactionItem ¶
Bases: SonnysModel
A line item within a transaction detail.
Contains the item name, SKU, department, quantity, and price breakdown (gross, net, discount, tax).
TransactionDiscount ¶
Bases: SonnysModel
A discount applied within a transaction detail.
Records the discount name, code, amount, and which item it was applied to.
Stats¶
SalesResult ¶
Bases: SonnysModel
Revenue breakdown returned by client.stats.total_sales().
Categorizes total revenue into three buckets based on transaction flags: recurring plan sales, recurring redemptions, and retail. Each bucket includes both a revenue total and a transaction count.
WashResult ¶
Bases: SonnysModel
Wash volume breakdown returned by client.stats.total_washes().
Categorizes wash transactions using v2 flags, v1 type=wash,
and v1 type=recurring:
- member_wash_count: Transactions where
is_recurring_plan_redemptionisTrue(membership washes). - retail_wash_count: Non-member car washes —
type=washtransactions (excluding plan sales and redemptions) plus unknown non-negative transaction types. - free_wash_count: Washes with
total == 0. - eligible_wash_count: Derived as
total - member_wash_count - free_wash_count. Used as the denominator in conversion rate calculations. Includes plan sale washes and positive-total unknown types.
The total field is member + retail + plan_sale_washes.
Negative-total transactions (refunds) are excluded entirely.
ConversionResult ¶
Bases: SonnysModel
Membership conversion KPI returned by client.stats.conversion_rate().
Measures how effectively a site converts eligible wash customers
into membership sign-ups. The rate is computed as
new_memberships / eligible_washes.
A rate of 0.15 means 15 % of eligible washes resulted in a
new membership sale. When there are zero eligible washes the rate
is 0.0 (division-by-zero safe).
LaborCostResult ¶
Bases: SonnysModel
Labor cost breakdown returned by client.stats.total_labor_cost().
Aggregates pay data from all clock entries in the requested period,
splitting costs into regular and overtime buckets. Each cost is
computed as rate * hours for the corresponding pay type, then
summed across all entries.
Attributes:
| Name | Type | Description |
|---|---|---|
total_cost |
float
|
Combined regular and overtime cost
( |
regular_cost |
float
|
Sum of |
overtime_cost |
float
|
Sum of |
regular_hours |
float
|
Total regular hours worked across all entries. |
overtime_hours |
float
|
Total overtime hours worked across all entries. |
total_hours |
float
|
Combined hours ( |
entry_count |
int
|
Number of clock entries aggregated. |
CostPerCarResult ¶
Bases: SonnysModel
Cost-per-car KPI returned by client.stats.cost_per_car().
Measures labor efficiency by dividing total labor cost by the
number of washes in the period. A value of 4.25 means the
site spent $4.25 in labor for each car washed.
When there are zero washes the cost_per_car is 0.0
(division-by-zero safe).
Attributes:
| Name | Type | Description |
|---|---|---|
cost_per_car |
float
|
Total labor cost divided by total washes
( |
total_labor_cost |
float
|
Aggregate labor cost from
:class: |
total_washes |
int
|
Wash count from :class: |
StatsReport ¶
Bases: SonnysModel
Unified analytics report returned by client.stats.report().
Bundles all KPIs into a single result object, computed from a single v2 transaction fetch for efficiency.
Attributes:
| Name | Type | Description |
|---|---|---|
sales |
SalesResult
|
Revenue breakdown (recurring plan sales, recurring
redemptions, and retail) as a :class: |
washes |
WashResult
|
Wash volume breakdown (retail, member, and eligible
washes) as a :class: |
new_memberships |
int
|
Count of recurring plan sales during the report period. |
conversion |
ConversionResult
|
Membership conversion rate KPI as a
:class: |
labor |
LaborCostResult
|
Labor cost breakdown (regular/overtime costs and hours)
as a :class: |
cost_per_car |
CostPerCarResult
|
Labor cost per car KPI as a
:class: |
period_start |
str
|
ISO-8601 date string for the start of the report
range (e.g. |
period_end |
str
|
ISO-8601 date string for the end of the report
range (e.g. |
BackOffice¶
TimesheetShift ¶
Bases: SonnysModel
A single punch-in/punch-out shift for one employee.
One BackOffice detail row = one shift. Employees can have multiple shifts per day, potentially at different sites.
When an employee is still clocked in at the moment the report was
rendered (i.e. the shift is in progress), date_out and
time_out are None. Use :attr:is_open to test for this
cleanly. BackOffice still reports regular_hours, regular_wages,
and total_wages for open shifts based on elapsed time up to the
moment the page was rendered.
is_open
property
¶
is_open: bool
True when the shift has no clock-out yet (employee is
still on the clock at the moment the report was rendered).
EmployeeTimesheet ¶
Bases: SonnysModel
All shifts for one employee in the reporting period, plus their
per-employee rollup totals from the "Total for
BackOfficeTimeclockResult ¶
Bases: SonnysModel
Complete employee-timesheets / timeclock report for a date range.
Returned by :meth:BackOfficeResource.timeclock. Contains per-shift
detail nested under per-employee rollups, plus grand totals parsed
from the final "Timesheet Total:" footer row.