How to read these
Three walkthroughs for the jobs people build first. Each one uses only calls that are in the reference, with the same parameters. Every answered call costs 1 credit, a refusal costs nothing, and there is no rate limit; see credits. Replace hh_live_... with your own key from API keys. Before any of them, call Whoami once: it gives you the company_id, job_type_id and technician ids the examples use.
Recipe 1: a web form becomes a job
Goal: when a customer fills in the form on a client's website, a job appears in HeadHoncho. You need a key with do on the Jobs area.
- Call Whoami and keep the client's
company_idand thejob_type_idfor the kind of work the form is for. - When the form is submitted, your server calls Create job. Use
"intent": "draft"to open a lead, or leave it out to open a booked job. - Send the form's own submission id as the
Idempotency-Key. If the customer presses the button twice, or your server retries after a dropped connection, HeadHoncho returns the first answer and makes one job. See the model. - If the answer is a 422, the
error.fieldsobject names each field to fix. Show those to the customer. Any other error carries acodeyou can look up on errors. - To show a confirmation, read the job back with Get job and
?after=<position>from the first answer: the read waits until the new job is there. See see your own write.
import requests
API = "https://api.headhoncho.app/api/v1"
KEY = "hh_live_..." # keep it on your server, never in the page
def job_from_form(form):
"""Called once for each form submission your site receives."""
response = requests.post(
f"{API}/commands/create-job",
headers={
"Authorization": f"Bearer {KEY}",
# The form's own submission id: a double submit makes one job.
"Idempotency-Key": f"web-form-{form['submission_id']}",
},
json={
"company_id": "cmp_14f5",
"client_name": form["name"],
"client_phone": form["phone"],
"address": {
"line1": form["street"],
"city": form["city"],
"state": form["state"],
"postal_code": form["zip"],
},
"job_type_id": "jt_02",
"notes": form["message"],
"intent": "draft", # a lead, until someone books it
},
)
if response.status_code == 422:
return {"ok": False, "fields": response.json()["error"]["fields"]}
response.raise_for_status()
return {"ok": True, "job_id": response.json()["job_id"]}
Do the call from your server, not from the visitor's browser. A key in a web page is a key anyone can copy.
Recipe 2: payments to bookkeeping
Goal: every month, copy what was paid, charged and spent on closed jobs into your books. You need a key with read on Jobs and on Money.
- Page through the closed jobs for the month with List jobs:
status=closed, afromdate andlimit=200.fromandtopick jobs by the day they were created, so start earlier than the month. Each answer hasitemsand anextcursor. Passnextback ascursoruntil it isnull. - For each job, call Get job money. Its
lineshold every payment, fee and expense on the job, and itscurrencynames the money. - Skip any line with
voided: true. A voided line counts in no figure. - Keep only the lines whose
ondate is inside the month. Write one row per line.amountis a plain number: keep it as a decimal in your books, not text.onis a date in the company's time zone. - Going the other way, when your books show a payment that HeadHoncho does not, record it with Record payment. Send an
Idempotency-Keybuilt from your own payment id, so running the job twice never records it twice.
import requests
API = "https://api.headhoncho.app/api/v1"
HEADERS = {"Authorization": "Bearer hh_live_..."}
def closed_jobs(created_from):
cursor = None
while True:
params = {"status": "closed", "from": created_from, "limit": 200}
if cursor:
params["cursor"] = cursor
page = requests.get(f"{API}/jobs", headers=HEADERS, params=params).json()
yield from page["items"]
cursor = page["next"]
if cursor is None:
return
def ledger_rows(first_day, last_day):
# "from" picks jobs by the day they were created, so start earlier than the
# month and keep only the lines whose own date is inside it.
for job in closed_jobs("2026-01-01"):
money = requests.get(f"{API}/jobs/{job['id']}/money", headers=HEADERS).json()
for line in money["lines"]:
if line["voided"]:
continue # a voided line counts in no figure
if not first_day <= line["on"] <= last_day:
continue # dates are YYYY-MM-DD, so text order works
yield {
"job": money["job"]["number"],
"date": line["on"],
"kind": line["kind"], # payment, fee, expense or cut
"amount": line["amount"], # a number, in money["currency"]
"memo": line["memo"],
}
This walk costs one credit for each page of jobs and one for each job. For a large month, run it once, not on a timer.
Recipe 3: Zapier
Goal: a new row in a spreadsheet, or a form your client already uses, creates a job without code. You need a key with do on Jobs. Zapier's menus change; what matters is the URL, the three headers and the JSON body.
- Step 1, the trigger: pick the app that holds the lead, such as a new spreadsheet row or a new form response.
- Step 2, the action: add Webhooks by Zapier and choose the Custom Request event. It calls Create job. Fill it in as the table says.
- Use the trigger's row id as the
Idempotency-Key. If Zapier replays the step, HeadHoncho makes one job. - Test the step. The answer has
accepted: trueand the newjob_id. Add a third step that writesjob_idback to the row if you want a link between them.
| Zapier field | Value |
|---|---|
| Event | Custom Request (Webhooks by Zapier) |
| Method | POST |
| URL | https://api.headhoncho.app/api/v1/commands/create-job |
| Headers | Authorization = Bearer hh_live_..., Content-Type = application/json, Idempotency-Key = the sheet row id from step 1 |
| Data | The JSON body below, with the row's columns dragged in |
{
"company_id": "cmp_14f5",
"client_name": "<Name column>",
"client_phone": "<Phone column>",
"address": { "line1": "<Street column>", "city": "<City column>",
"state": "<State column>", "postal_code": "<Zip column>" },
"notes": "<Message column>",
"intent": "draft"
}
The same pattern works for any command: change the URL to the command's page, and send the parameters that page lists. Recording a payment from another app is Record payment in the same way.
To start a Zap when something changes in HeadHoncho, do not poll. Use a webhook:
- Make a Zap whose trigger is Webhooks by Zapier and the event Catch Hook. Zapier gives you an address that starts with
https://hooks.zapier.com/. - Add that address as a webhook, in the app or with Add webhook, and pick the kinds of change that should start the Zap, such as
job.createdorjob.status_changed. - Send a test with Send webhook test so Zapier learns the fields, then build the rest of the Zap from them.
- Zapier does not check our signature; it only needs the address. The address is the secret, so keep it private. Each change is one run of the Zap, and costs one credit.
Read webhooks for what is sent and how retries work.
Next: the model for the rules every call follows, errors for what a refusal says, and building with HeadHoncho if a coding agent is writing the code.