The Housecall Pro API Ruby client
The Housecall Pro API, answered in the vocabulary of the company gem: a key opens an account, the account answers the business it belongs to, its leads and its jobs, and nothing else. Where a caller reads nothing, this gem has no method.
How to install
gem install hcpOr, in a Gemfile, pinned to the current major:
gem 'hcp', '~> 3.0'~> major.minor means bundle update never crosses a breaking change.
The account
An account is a key, acting as one location where the key opens several:
account = Hcp::Account.new key: 'the-api-key'
account = Hcp::Account.new key: 'the-api-key', company_id: 'loc_1'company_id: is sent as X-Company-Id. The account a key belongs to refuses the header, so it
is only sent where a location is named.
The business
business = account.business
business.id, business.name
business.phone # => '5555550100', ten digits however Housecall Pro wrote them, or nil
business.subsidiaries # => the business itself first, then every location under it, flatHousecall Pro answers a franchise as a tree several levels deep; subsidiaries reads it flat
and is never empty, and each id in it is what company_id: takes.
Leads
Opening a lead opens its customer with it, and hands back what Housecall Pro filed both under:
lead = account.leads.create name: 'Ada', surname: 'Lovelace', phone: '5550000001',
email: 'ada@example.com', description: 'Fix the sink', notes: 'Very interested in buying',
address: { street: '1 Example Street', city: 'Springfield', state: 'CA', zip: '90210' },
source: 'The Lead Generator'
lead.id, lead.customer.idMoving a lead through the pipeline names the status as the account names it; a name the
account has no status for raises Hcp::Error:
account.leads.find('lea_1').update status_name: 'Won'Jobs
The jobs booked to start within a window are walked a page at a time, each read flat and in dollars however Housecall Pro nested it or counted it:
account.jobs.past(4.weeks).each do |job|
job.id, job.description, job.created_at, job.scheduled_at, job.completed_at
job.notes # => "- Gate code 1234\n- Dog in the yard", one to a line
job.amount # => 330.0, dollars as a BigDecimal, where Housecall Pro said 33000
job.quote # => an Hcp::Quote, the estimate option the job was created from, or nil
job.quote.amount # => that option's total in dollars, found among the customer's estimates
job.location # => an Hcp::Location, or nil where the job is booked nowhere
job.location.customer # => an Hcp::Customer: id, name, surname, email, phone
job.lines # => Hcp::Line, read off the job's own line_items endpoint on first ask
endA customer's name is their first name, or the business's where a person has none, and their
phone is the first of the mobile, home and work numbers that can be dialed.
Visits
A visit is any booked time, and Housecall Pro books it two ways. Work already won is a job, and Housecall Pro calls its stops appointments and files them inside it. Work still being looked at is an estimate, which it schedules the same way but hangs no appointments under, so an estimate holds the one slot. Both are read off the work booked across the window, a page of it at a time; work called off keeps its stops to itself, and a list nothing narrows walks every job and estimate there was.
account.visits.upcoming(2.weeks).each do |visit|
visit.id, visit.starts_at, visit.ends_at, visit.anytime?
visit.description # => what the job is called, or nil: an estimate has no words of its own
visit.location # => where the stop is, whatever it was booked for
visit.job # => the Hcp::Job the stop belongs to, nil on an estimate or an event
visit.lead # => the Hcp::Estimate it belongs to, nil on a job or an event
visit.technicians # => the Hcp::Technicians the stop is booked for
endTime blocked out around the work counts as booked too, and Housecall Pro files it as an event: a hold, a day off, an hour that is simply not free. An event that repeats is stored once, as the hour it first takes and the iCalendar rule it repeats by, so the hours after that are worked out here rather than read -- in the time zone the event is kept in, so an hour that holds at ten in the morning still holds at ten once the clocks have gone back. Each one reads as a visit standing on no job and no lead, and answers to the event's ID and the moment it starts, there being one ID for the whole rule.
Housecall Pro narrows events by nothing -- it accepts a window and a crew on /events and
ignores both -- so a week of them is read by sweeping every page and keeping what the week
holds. That is the one expensive part of the list, and the one way to be spared it is to ask
for the stops of work alone:
account.visits.upcoming(2.weeks).for_jobs # => only the appointments, one request
account.visits.upcoming(2.weeks).for_leads # => only the estimates' slots, one request
account.visits.upcoming(2.weeks).for_work # => both kinds of stop, and no sweepfor_work is the one to ask for where a caller wants the work and not the hours held around it:
the jobs and the estimates are still read, and the fifteen requests of the sweep are not spent.
A stop says where it is without being asked what it was booked for: an appointment has no
address of its own and takes the job's, and an estimate's slot takes the estimate's. A caller
reading a schedule never reaches through visit.job for an address.
An estimate reads as the lead it is -- id, customer, location -- because its other half,
the price, is already Hcp::Quote: Housecall Pro files the visit and the prices as one record
and the vocabulary reads them as two.
account.visits.create is named by the vocabulary and not answered here yet; it raises
NotImplementedError. Booking one means POST /estimates, which takes a customer_id rather
than a customer, so it needs GET /customers and POST /customers first, and all three want
probing before they are written against.
The schedule
The crew are Housecall Pro's employees, and the active ones are walked a page at a time:
account.technicians.each do |technician|
technician.id, technician.name, technician.surname
endOne technician's week is the visits in it narrowed to them, which is how a schedule reads:
monday = Date.today.beginning_of_week.in_time_zone
account.visits.between(monday, monday + 1.week).of(technician.id).each do |visit|
visit.starts_at, visit.ends_at, visit.job.location.street
endHousecall Pro narrows both lists by who is assigned to the work, so the window is asked for as that technician's and nobody else's comes back -- verified against a real account, where a year of work went from 15 jobs to 5 and from 40 estimates to 2. It narrows no further: a job's appointments are dispatched to some of its crew or to none of it, so a stop dispatched to nobody is the whole crew's, and the stops the technician is not on are let go once the jobs arrive. Asking for the week and asking for the technician narrow the same list, in either order.
The other half of that week is the hours they are not out, which Housecall Pro works out for itself and answers whole:
account.windows.between(monday, monday + 1.week).of(technician.id).each do |window|
window.starts_at, window.ends_at
endIt holds the hours the business keeps, the notice it needs, the padding it leaves around a job and the time blocked out on the calendar, so this asks rather than reckons and the holds are already taken off. It answers a week as a row of short windows, each open or not; the open ones that run together are one stretch and are joined back into it, so a window is as long as the pro is free rather than as long as their booking page happens to offer.
start_date, show_for_days and employee_ids all narrow, so a week for one person is the one
request -- unlike /events, which accepts the same words and ignores them.
What Housecall Pro schedules elsewhere is still not here: time blocked out on the calendar is
filed under /events, which takes no date and no employee to narrow by, so a week of it cannot
be asked for -- only paged in full.
Errors
Everything descends from Hcp::Error, which descends from Company::Error, so one rescue
still catches the lot. Hcp::Throttled, a Company::Throttled, is a refusal for rate, so one
retry covers every platform.
Nothing here sleeps. A caller told to come back later has a queue that can bring the whole job back, which is worth more than a worker asleep holding a connection open.
Webhooks
Hcp::Event reads a webhook payload, and reaches the network for nothing. The signature and
timestamp headers Housecall Pro signs one with are Hcp::Event::SIGNATURE_HEADER and
Hcp::Event::TIMESTAMP_HEADER.
event = Hcp::Event.new params
event.lead_id, event.customer_id, event.conversion_type, event.conversion_id
event.job_id, event.estimate_id, event.scheduled_at, event.completed_at
event.invoice_id, event.invoice_job_id, event.invoice_amount