# 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.

Page: https://headhoncho.app/docs/api/recipes | Part of the HeadHoncho API docs: https://headhoncho.app/docs/api | Machine-readable: https://headhoncho.app/docs/api/openapi.json | Everything in one file: https://headhoncho.app/docs/api/llms-full.txt

## How to read these

Three walkthroughs for the jobs people build first. Each one uses only calls that are in the [reference](https://headhoncho.app/docs/api), with the same parameters. Every answered call costs 1 credit, a refusal costs nothing, and there is no rate limit; see [credits](https://headhoncho.app/docs/api/errors#credits). Replace `hh_live_...` with your own key from [API keys](https://headhoncho.app/docs/api/keys). Before any of them, call [Whoami](https://headhoncho.app/docs/api/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](https://headhoncho.app/docs/api/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](https://headhoncho.app/docs/api/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](https://headhoncho.app/docs/api/model#idempotency).
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](https://headhoncho.app/docs/api/errors).
5. To show a confirmation, read the job back with [Get job](https://headhoncho.app/docs/api/get_job) and `?after=<position>` from the first answer: the read waits until the new job is there. See [see your own write](https://headhoncho.app/docs/api/model#position).

```python
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](https://headhoncho.app/docs/api/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](https://headhoncho.app/docs/api/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](https://headhoncho.app/docs/api/record_payment). Send an `Idempotency-Key` built from your own payment id, so running the job twice never records it twice.

```python
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](https://headhoncho.app/docs/api/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 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 |

```json
{
  "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](https://headhoncho.app/docs/api/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](https://headhoncho.app/docs/api/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](https://headhoncho.app/docs/api/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](https://headhoncho.app/docs/api/webhooks) for what is sent and how retries work.

Next: [the model](https://headhoncho.app/docs/api/model) for the rules every call follows, [errors](https://headhoncho.app/docs/api/errors) for what a refusal says, and [building with HeadHoncho](https://headhoncho.app/docs/api/agents) if a coding agent is writing the code.
