HeadHoncho

Recipes

Three walkthroughs for the HeadHoncho API: a web form that makes a job, payments copied to your books, and a Zapier step that makes jobs or starts on a webhook.

Read as Markdown

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.

  1. Call Whoami and keep the client's company_id and the job_type_id for the kind of work the form is for.
  2. 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.
  3. 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.
  4. If the answer is a 422, the error.fields object names each field to fix. Show those to the customer. Any other error carries a code you can look up on errors.
  5. 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.

  1. Page through the closed jobs for the month with List jobs: status=closed, a from date and limit=200. from and to pick jobs by the day they were created, so start earlier than the month. Each answer has items and a next cursor. Pass next back as cursor until it is null.
  2. For each job, call Get job money. Its lines hold every payment, fee and expense on the job, and its currency names the money.
  3. Skip any line with voided: true. A voided line counts in no figure.
  4. Keep only the lines whose on date is inside the month. Write one row per line. amount is a plain number: keep it as a decimal in your books, not text. on is a date in the company's time zone.
  5. Going the other way, when your books show a payment that HeadHoncho does not, record it with Record payment. Send an Idempotency-Key built 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.

  1. Step 1, the trigger: pick the app that holds the lead, such as a new spreadsheet row or a new form response.
  2. 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.
  3. Use the trigger's row id as the Idempotency-Key. If Zapier replays the step, HeadHoncho makes one job.
  4. Test the step. The answer has accepted: true and the new job_id. Add a third step that writes job_id back to the row if you want a link between them.
Zapier fieldValue
EventCustom Request (Webhooks by Zapier)
MethodPOST
URLhttps://api.headhoncho.app/api/v1/commands/create-job
HeadersAuthorization = Bearer hh_live_..., Content-Type = application/json, Idempotency-Key = the sheet row id from step 1
DataThe 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:

  1. 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/.
  2. 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.created or job.status_changed.
  3. Send a test with Send webhook test so Zapier learns the fields, then build the rest of the Zap from them.
  4. 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.