Houseler API
A REST API for reading and creating customers, bookings, and invoices in your Houseler account. It is the same API that powers the Houseler apps and the Houseler integration for Zapier.
Every request is scoped to the single company its API key belongs to. There is no cross-company access, and no endpoint accepts a company id as a parameter.
Base URL
https://houseler.comAll paths below are relative to this host. Requests and responses are JSON; send Content-Type: application/json on any request with a body.
Authentication
Authenticate with a per-company API key, sent in the X-Api-Key header:
curl https://houseler.com/api/zapier/me \
-H "X-Api-Key: YOUR_API_KEY"To create a key, sign in to Houseler and open Settings → Integrations. The key is shown once, at creation — store it somewhere safe, because it cannot be retrieved again. You can issue as many keys as you need and revoke any of them at any time.
A missing, unknown, or revoked key returns 401 Unauthorized on every endpoint.
Conventions
- Money
- Always integer cents, never a decimal. $150.00 is 15000. Endpoints that return money also expose a matching *Dollars string ("150.00") where noted.
- Phone numbers
- US numbers, normalised to E.164 on write. Both +15125550147 and 5125550147 are accepted; anything that is not a valid 10-digit US number is rejected with a 400. Phone is unique per company and is how Houseler recognises a returning customer.
- Dates and times
- Sent and returned as ISO 8601 strings. Timestamps are UTC; supply an offset on any time you write so it is unambiguous.
- Deletions
- Records are soft-deleted. Deleted rows never appear in any response documented here.
- Pagination
- Where supported, via page (zero-based) and pageSize. The polling feeds are intentionally not paginated — they return the newest 50 records.
Reading data
The company the API key belongs to.
Returns { companyId, companyName, brand }. Use it to verify a key is valid and to label a connection. Returns 401 if the key is missing, unknown, or revoked.
Customers, newest first.
Ordered by createdAt descending, capped at 50, soft-deleted rows excluded. Each row includes the customer’s current address. Built for polling: the ordering key is immutable, so page 1 is stable and rows can be de-duplicated on id.
Bookings, newest first.
Ordered by createdAt descending, capped at 50. Recurring series are returned once as stored, not expanded into individual occurrences. Includes the linked customer, service, and team member.
Paid invoices, newest first.
Ordered by paidAt descending — the moment the invoice was paid, not when it was created — so a back-dated invoice paid today still appears first. Money fields are integer cents; each also has a *Dollars string mirror (total / totalDollars) so you can map straight into accounting tools without a conversion step.
List customers, or look one up by phone.
Without parameters, returns a page of customers (page, pageSize; pageSize defaults to 50). With ?phone=<number>, returns the single matching customer, or 404 if there is none — phone is unique per company, so this is an exact-match lookup rather than a search.
The services this company offers.
Each row has an id and a name, plus its configured prices.
Picker options
Compact id-and-label lists, intended to populate a dropdown in an integration UI rather than to be consumed as data.
Customer picker options.
Returns [{ id, name }] where name is "First Last — phone", 100 per page via ?page=. Phone is included because it is the disambiguator when two customers share a first name.
Booking picker options.
Returns [{ id, name, start }]. The label is rendered in the company’s own timezone, so it reads as the operator scheduled it rather than in UTC.
Team member picker options.
Returns [{ id, name, role }].
Creating records
Create a customer.
Required: firstName, phone. Optional: lastName, email, addressLine1, addressLine2, city, state, zip, timezone, repeatCustomer.
There is no single name field. Because phone is unique per company, posting a number that already exists returns 409 Conflict rather than creating a duplicate — look the customer up first if you want find-or-create behaviour.
curl -X POST https://houseler.com/api/customer \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"firstName":"Dana","lastName":"Whitfield","phone":"+13105550162"}'Create a booking.
Required: start and end (start must be before end), plus either customerId or both customerPhone and customerFirstName. Passing phone and first name lets Houseler match an existing customer by phone, or create one if there is no match.
Optional: title, notes, productId, employeeId, customerLastName, and a nested address object. Bookings cannot overlap an existing one, so a taken slot returns 409 Conflict.
curl -X POST https://houseler.com/api/appointment \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"start": "2026-09-10T16:00:00.000Z",
"end": "2026-09-10T17:30:00.000Z",
"customerPhone": "+13105550162",
"customerFirstName": "Dana",
"title": "Full detail"
}'Create a draft invoice.
Required: customerId, and lineItems with at least one entry. Each line item needs a name and a unitPrice, and may include quantity (defaults to 1) and description.
Optional: notes, taxRate, dueDate, appointmentId. Omit taxRate to use the company default; send 0 for no tax. New invoices are created with status Draft.
unitPrice is in cents. A $150.00 line item is 15000, not 150. Sending dollars will under-bill by a factor of 100.curl -X POST https://houseler.com/api/invoice \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customerId": "CUSTOMER_ID",
"lineItems": [
{ "name": "Full detail", "unitPrice": 15000, "quantity": 1 }
]
}'Errors
Errors return the appropriate status with a JSON body:
{ "error": "Customer not found", "code": "NOT_FOUND" }Validation failures add a details object naming the fields at fault.
| Status | Meaning |
|---|---|
| 400 | The request body failed validation. |
| 401 | The API key is missing, unknown, or revoked. |
| 403 | The key is valid but not permitted to do this. |
| 404 | No matching record in this company. |
| 409 | The write conflicts with an existing record — a duplicate phone number, or an overlapping booking. |
| 500 | Something failed on our side. Safe to retry. |
Using Zapier instead
If you would rather not write code, the Houseler integration for Zapier is built on these endpoints and covers the common cases: triggers for new customers, new bookings, and paid invoices, and actions to create customers, bookings, and invoices. It authenticates with the same API key.
Triggers poll for changes every few minutes rather than firing instantly. More about the Zapier integration.
Questions
Email hello@houseler.com. Include the endpoint and the time of the request and we can look it up.