{
  "openapi": "3.1.0",
  "info": {
    "title": "HeadHoncho public API",
    "version": "v1",
    "description": "Jobs, raw ad data and Google click ids for the companies a key reaches. Reads are GET; changes are POST /commands/<verb> with an Idempotency-Key. Guides: https://headhoncho.app/docs/api"
  },
  "servers": [
    {
      "url": "https://api.headhoncho.app"
    }
  ],
  "tags": [
    {
      "name": "Account"
    },
    {
      "name": "Jobs"
    },
    {
      "name": "Job commands"
    },
    {
      "name": "Raw data"
    },
    {
      "name": "Click IDs and conversions"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "paths": {
    "/api/v1/whoami": {
      "get": {
        "operationId": "whoami",
        "summary": "Whoami",
        "description": "Who this key or app acts as, which companies it reaches, what it may do, and the words to use in commands: statuses, job types and technicians. The workspace, the credential, the companies it may name, its access per area, and the lookups a command needs: every status, job type and technician, with ids. Lookups are carried here so a client never has to guess a name.",
        "tags": [
          "Account"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [],
        "x-headhoncho": {
          "area": "",
          "need": "read",
          "cost": "read",
          "mcp_tool": "whoami",
          "docs": "https://headhoncho.app/docs/api/whoami"
        },
        "responses": {
          "200": {
            "description": "The workspace, the credential, the companies it may name, its access per area, and the lookups a command needs: every status, job type and technician, with ids. Lookups are carried here so a client never has to guess a name.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "workspace": {
                    "id": "org_2c7d",
                    "name": "Northwind Ads"
                  },
                  "credential": {
                    "kind": "key",
                    "name": "Reporting laptop",
                    "acts_as": "Ariel T.",
                    "hide_personal_details": false
                  },
                  "companies": [
                    {
                      "id": "cmp_14f5",
                      "name": "Sunrise Moving",
                      "time_zone": "America/Chicago"
                    },
                    {
                      "id": "cmp_8a21",
                      "name": "Capital Roofing",
                      "time_zone": "America/New_York"
                    }
                  ],
                  "access": {
                    "jobs": "do",
                    "raw": "read",
                    "conv": "read"
                  },
                  "statuses": [
                    {
                      "id": "st_01",
                      "name": "Draft",
                      "slug": "draft"
                    },
                    {
                      "id": "st_02",
                      "name": "Scheduled",
                      "slug": "scheduled"
                    },
                    {
                      "id": "st_03",
                      "name": "Dispatched",
                      "slug": "dispatched"
                    },
                    {
                      "id": "st_06",
                      "name": "Closed",
                      "slug": "closed"
                    }
                  ],
                  "job_types": [
                    {
                      "id": "jt_02",
                      "name": "Local move"
                    },
                    {
                      "id": "jt_05",
                      "name": "Roof repair"
                    }
                  ],
                  "technicians": [
                    {
                      "id": "wrk_3b90",
                      "name": "Dana R.",
                      "role": "technician"
                    },
                    {
                      "id": "wrk_7a12",
                      "name": "Omar S.",
                      "role": "subcontractor"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "missing_key: Send your key as Authorization: Bearer hh_live_... unknown_key: That key is not one we issued, or it was revoked. expired_token: That token has expired. Refresh it, or connect the app again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: Too many calls this minute. Wait and try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/jobs": {
      "get": {
        "operationId": "list_jobs",
        "summary": "List jobs",
        "description": "Jobs and leads in the companies this credential reaches, newest first, with status, technician, schedule, money and the ad click each came from. items, one job each in the Get a job shape; next, a cursor for the following page or null; count, how many match in all. Trashed jobs are never listed.",
        "tags": [
          "Jobs"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "company_id",
            "in": "query",
            "required": false,
            "description": "One company. Default: every company the credential reaches.",
            "schema": {
              "type": "string"
            },
            "example": "cmp_14f5"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "A status name or slug from Who am I.",
            "schema": {
              "type": "string"
            },
            "example": "scheduled"
          },
          {
            "name": "worker_id",
            "in": "query",
            "required": false,
            "description": "Jobs this technician or subcontractor is on.",
            "schema": {
              "type": "string"
            },
            "example": "wrk_3b90"
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Created on or after this day, in the company's time zone.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-10-01"
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Created on or before this day.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-10-31"
          },
          {
            "name": "leads",
            "in": "query",
            "required": false,
            "description": "include (default), only, or exclude. One of include, only, exclude.",
            "schema": {
              "type": "string",
              "enum": [
                "include",
                "only",
                "exclude"
              ]
            },
            "example": "include"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1 to 200. Default 50.",
            "schema": {
              "type": "number"
            },
            "example": 50
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "From the previous answer's next.",
            "schema": {
              "type": "string"
            },
            "example": "eyJhZnRlciI6..."
          }
        ],
        "x-headhoncho": {
          "area": "jobs",
          "need": "read",
          "cost": "read",
          "mcp_tool": "list_jobs",
          "docs": "https://headhoncho.app/docs/api/list_jobs"
        },
        "responses": {
          "200": {
            "description": "items, one job each in the Get a job shape; next, a cursor for the following page or null; count, how many match in all. Trashed jobs are never listed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "items": [
                    {
                      "id": "job_1138",
                      "number": 1138,
                      "company": {
                        "id": "cmp_14f5",
                        "name": "Sunrise Moving"
                      },
                      "client": {
                        "id": "cli_77a0",
                        "name": "R. Azoulay",
                        "phone": "+12175550142",
                        "second_phone": ""
                      },
                      "address": {
                        "line1": "2590 Main St",
                        "line2": "",
                        "city": "Bloomington",
                        "state": "IL",
                        "postal_code": "61701",
                        "one_line": "2590 Main St, Bloomington, IL 61701"
                      },
                      "job_type": {
                        "id": "jt_02",
                        "name": "Local move"
                      },
                      "status": {
                        "id": "st_02",
                        "name": "Scheduled",
                        "slug": "scheduled",
                        "since": "2026-10-12T15:04:10Z"
                      },
                      "technicians": [
                        {
                          "id": "wrk_3b90",
                          "name": "Dana R."
                        }
                      ],
                      "scheduled_start": "2026-10-16T09:00:00-05:00",
                      "scheduled_end": "2026-10-16T11:00:00-05:00",
                      "quoted": 1450.0,
                      "collected": 0.0,
                      "profit": 0.0,
                      "currency": "USD",
                      "source": {
                        "label": "Google Ads",
                        "filled_by": "call",
                        "click_id": "Cj0KCQjw...",
                        "call_id": "CAL8f2e1",
                        "call_at": "2026-10-11T18:22:03Z",
                        "campaign": "Moving - Bloomington"
                      },
                      "is_lead": false,
                      "notes": "Two flights of stairs",
                      "created_at": "2026-10-11T18:25:40Z",
                      "closed_at": null,
                      "canceled_at": null
                    }
                  ],
                  "next": "eyJhZnRlciI6IjIwMjYtMTAtMTFUMTg6MjU6NDBaIn0",
                  "count": 128
                }
              }
            }
          },
          "401": {
            "description": "missing_key: Send your key as Authorization: Bearer hh_live_... unknown_key: That key is not one we issued, or it was revoked. expired_token: That token has expired. Refresh it, or connect the app again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "no_access: This credential does not reach that. Ask the person who made it for access.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found: No such record in the companies this credential reaches. out_of_scope: No such company in the companies this credential reaches.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid: Some fields were refused. See fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: Too many calls this minute. Wait and try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/jobs/{job_id}": {
      "get": {
        "operationId": "get_job",
        "summary": "Get job",
        "description": "One job or lead: who, where, when, which status, which technicians, the money on it and the ad click it came from. The job. Money is the price quoted, what was collected, and the profit after the subcontractor, each as a number in the company's currency. source is how the job was attributed: filled_by says whether the click came off a tracked call or the job's own record.",
        "tags": [
          "Jobs"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "description": "The job id, or its number.",
            "schema": {
              "type": "string"
            },
            "example": "job_1138"
          }
        ],
        "x-headhoncho": {
          "area": "jobs",
          "need": "read",
          "cost": "read",
          "mcp_tool": "get_job",
          "docs": "https://headhoncho.app/docs/api/get_job"
        },
        "responses": {
          "200": {
            "description": "The job. Money is the price quoted, what was collected, and the profit after the subcontractor, each as a number in the company's currency. source is how the job was attributed: filled_by says whether the click came off a tracked call or the job's own record.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "job_1138",
                  "number": 1138,
                  "company": {
                    "id": "cmp_14f5",
                    "name": "Sunrise Moving"
                  },
                  "client": {
                    "id": "cli_77a0",
                    "name": "R. Azoulay",
                    "phone": "+12175550142",
                    "second_phone": ""
                  },
                  "address": {
                    "line1": "2590 Main St",
                    "line2": "",
                    "city": "Bloomington",
                    "state": "IL",
                    "postal_code": "61701",
                    "one_line": "2590 Main St, Bloomington, IL 61701"
                  },
                  "job_type": {
                    "id": "jt_02",
                    "name": "Local move"
                  },
                  "status": {
                    "id": "st_02",
                    "name": "Scheduled",
                    "slug": "scheduled",
                    "since": "2026-10-12T15:04:10Z"
                  },
                  "technicians": [
                    {
                      "id": "wrk_3b90",
                      "name": "Dana R."
                    }
                  ],
                  "scheduled_start": "2026-10-16T09:00:00-05:00",
                  "scheduled_end": "2026-10-16T11:00:00-05:00",
                  "quoted": 1450.0,
                  "collected": 0.0,
                  "profit": 0.0,
                  "currency": "USD",
                  "source": {
                    "label": "Google Ads",
                    "filled_by": "call",
                    "click_id": "Cj0KCQjw...",
                    "call_id": "CAL8f2e1",
                    "call_at": "2026-10-11T18:22:03Z",
                    "campaign": "Moving - Bloomington"
                  },
                  "is_lead": false,
                  "notes": "Two flights of stairs",
                  "created_at": "2026-10-11T18:25:40Z",
                  "closed_at": null,
                  "canceled_at": null
                }
              }
            }
          },
          "401": {
            "description": "missing_key: Send your key as Authorization: Bearer hh_live_... unknown_key: That key is not one we issued, or it was revoked. expired_token: That token has expired. Refresh it, or connect the app again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "no_access: This credential does not reach that. Ask the person who made it for access.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found: No such record in the companies this credential reaches.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid: Some fields were refused. See fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: Too many calls this minute. Wait and try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/jobs/{job_id}/history": {
      "get": {
        "operationId": "get_job_history",
        "summary": "Get job history",
        "description": "Everything that happened to a job, in order, with who did it: created, moved, assigned, noted, paid. The same log the job page shows. items, oldest first: position (the event position, usable as after= on a read), event, at, by (a person's name, or \"Claude, for Ariel T.\" when an AI app acted), and the event's own facts.",
        "tags": [
          "Jobs"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "description": "The job id, or its number.",
            "schema": {
              "type": "string"
            },
            "example": "job_1138"
          }
        ],
        "x-headhoncho": {
          "area": "jobs",
          "need": "read",
          "cost": "read",
          "mcp_tool": "get_job_history",
          "docs": "https://headhoncho.app/docs/api/get_job_history"
        },
        "responses": {
          "200": {
            "description": "items, oldest first: position (the event position, usable as after= on a read), event, at, by (a person's name, or \"Claude, for Ariel T.\" when an AI app acted), and the event's own facts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "items": [
                    {
                      "position": 88120,
                      "event": "job.created",
                      "at": "2026-10-11T18:25:40Z",
                      "by": "Ariel T.",
                      "facts": {
                        "client_name": "R. Azoulay",
                        "intent": "create"
                      }
                    },
                    {
                      "position": 88131,
                      "event": "job.assigned",
                      "at": "2026-10-11T18:26:02Z",
                      "by": "Ariel T.",
                      "facts": {
                        "worker_id": "wrk_3b90"
                      }
                    },
                    {
                      "position": 88402,
                      "event": "job.note_added",
                      "at": "2026-10-12T15:04:10Z",
                      "by": "Claude, for Ariel T.",
                      "facts": {
                        "body": "Gate code 4410"
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "missing_key: Send your key as Authorization: Bearer hh_live_... unknown_key: That key is not one we issued, or it was revoked. expired_token: That token has expired. Refresh it, or connect the app again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "no_access: This credential does not reach that. Ask the person who made it for access.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found: No such record in the companies this credential reaches.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid: Some fields were refused. See fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: Too many calls this minute. Wait and try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/commands/create-job": {
      "post": {
        "operationId": "create_job",
        "summary": "Create job",
        "description": "Open a job or a lead in one company: the customer, the address, and optionally the job type, the time window and the technician. accepted, job_id, number, and position: the event position, usable as after= on the next read so you see your own write. A missing fact the app would hold on Draft is refused here with 422 naming the field.",
        "tags": [
          "Job commands"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Any unique string. The same key with the same body returns the first answer and runs nothing twice.",
            "schema": {
              "type": "string"
            },
            "example": "6f1d2c9e-1b7a-4c1e-9a52-0d3f8e4b7a10"
          }
        ],
        "x-headhoncho": {
          "area": "jobs",
          "need": "do",
          "cost": "write",
          "mcp_tool": "create_job",
          "docs": "https://headhoncho.app/docs/api/create_job"
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "company_id": {
                    "type": "string",
                    "description": "From Who am I."
                  },
                  "client_name": {
                    "type": "string",
                    "description": "The customer, as they gave it."
                  },
                  "client_phone": {
                    "type": "string",
                    "description": "Any format; stored as E.164."
                  },
                  "address": {
                    "type": "object",
                    "description": "line1, city, state and postal_code. line2 is optional."
                  },
                  "job_type_id": {
                    "type": "string",
                    "description": "From Who am I."
                  },
                  "scheduled_start": {
                    "type": "string",
                    "format": "date-time",
                    "description": "ISO 8601, with scheduled_end."
                  },
                  "scheduled_end": {
                    "type": "string",
                    "format": "date-time",
                    "description": "ISO 8601. The end of the window."
                  },
                  "quoted_price": {
                    "type": "number",
                    "description": "The price quoted, a number."
                  },
                  "worker_id": {
                    "type": "string",
                    "description": "Assigns the technician now."
                  },
                  "notes": {
                    "type": "string",
                    "description": "Anything the crew should know."
                  },
                  "intent": {
                    "type": "string",
                    "enum": [
                      "create",
                      "draft",
                      "dispatch"
                    ],
                    "description": "create (default) opens a job; draft opens a lead; dispatch opens it and sends the ticket to the technician. One of create, draft, dispatch."
                  }
                },
                "required": [
                  "company_id",
                  "client_name",
                  "client_phone",
                  "address"
                ]
              },
              "example": {
                "company_id": "cmp_14f5",
                "client_name": "R. Azoulay",
                "client_phone": "+12175550142",
                "address": {
                  "line1": "2590 Main St",
                  "city": "Bloomington",
                  "state": "IL",
                  "postal_code": "61701"
                },
                "job_type_id": "jt_02",
                "scheduled_start": "2026-10-16T09:00:00-05:00",
                "scheduled_end": "2026-10-16T11:00:00-05:00",
                "quoted_price": 1450,
                "worker_id": "wrk_3b90",
                "notes": "Two flights of stairs",
                "intent": "create"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "accepted, job_id, number, and position: the event position, usable as after= on the next read so you see your own write. A missing fact the app would hold on Draft is refused here with 422 naming the field.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "accepted": true,
                  "job_id": "job_1203",
                  "number": 1203,
                  "position": 88411,
                  "status": {
                    "id": "st_02",
                    "name": "Scheduled",
                    "slug": "scheduled"
                  }
                }
              }
            }
          },
          "400": {
            "description": "missing_idempotency_key: Send an Idempotency-Key header on every command.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "missing_key: Send your key as Authorization: Bearer hh_live_... unknown_key: That key is not one we issued, or it was revoked. expired_token: That token has expired. Refresh it, or connect the app again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "no_access: This credential does not reach that. Ask the person who made it for access. read_only: This credential may read this area but not change it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found: No such record in the companies this credential reaches. out_of_scope: No such company in the companies this credential reaches.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "idempotency_mismatch: This Idempotency-Key was used with a different body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid: Some fields were refused. See fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: Too many calls this minute. Wait and try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/commands/update-job": {
      "post": {
        "operationId": "update_job",
        "summary": "Update job",
        "description": "Change a job's facts: the customer, the address, the schedule, the price, the job type or the notes. Only the fields you send change. accepted, job_id, number, position, and changed: the fields that moved.",
        "tags": [
          "Job commands"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Any unique string. The same key with the same body returns the first answer and runs nothing twice.",
            "schema": {
              "type": "string"
            },
            "example": "6f1d2c9e-1b7a-4c1e-9a52-0d3f8e4b7a10"
          }
        ],
        "x-headhoncho": {
          "area": "jobs",
          "need": "do",
          "cost": "write",
          "mcp_tool": "update_job",
          "docs": "https://headhoncho.app/docs/api/update_job"
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "job_id": {
                    "type": "string",
                    "description": "The job id, or its number."
                  },
                  "client_name": {
                    "type": "string",
                    "description": "The customer."
                  },
                  "client_phone": {
                    "type": "string",
                    "description": "Any format; stored as E.164."
                  },
                  "client_secondary_phone": {
                    "type": "string",
                    "description": "A second phone, or empty to clear it."
                  },
                  "address": {
                    "type": "object",
                    "description": "All four parts, as for Create a job."
                  },
                  "job_type_id": {
                    "type": "string",
                    "description": "From Who am I."
                  },
                  "scheduled_start": {
                    "type": "string",
                    "format": "date-time",
                    "description": "ISO 8601. Send null to clear the window."
                  },
                  "scheduled_end": {
                    "type": "string",
                    "format": "date-time",
                    "description": "The end of the window."
                  },
                  "quoted_price": {
                    "type": "number",
                    "description": "The new price, a number."
                  },
                  "notes": {
                    "type": "string",
                    "description": "Replaces the notes."
                  }
                },
                "required": [
                  "job_id"
                ]
              },
              "example": {
                "job_id": "job_1203",
                "scheduled_start": "2026-10-16T14:00:00-05:00",
                "scheduled_end": "2026-10-16T17:00:00-05:00",
                "quoted_price": 1600
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "accepted, job_id, number, position, and changed: the fields that moved.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "accepted": true,
                  "job_id": "job_1203",
                  "number": 1203,
                  "position": 88411,
                  "changed": [
                    "scheduled_start",
                    "scheduled_end",
                    "quoted_price"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "missing_idempotency_key: Send an Idempotency-Key header on every command.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "missing_key: Send your key as Authorization: Bearer hh_live_... unknown_key: That key is not one we issued, or it was revoked. expired_token: That token has expired. Refresh it, or connect the app again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "no_access: This credential does not reach that. Ask the person who made it for access. read_only: This credential may read this area but not change it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found: No such record in the companies this credential reaches.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "idempotency_mismatch: This Idempotency-Key was used with a different body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid: Some fields were refused. See fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: Too many calls this minute. Wait and try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/commands/change-job-status": {
      "post": {
        "operationId": "change_job_status",
        "summary": "Change job status",
        "description": "Move a job to another status by its name, slug or id. The move lands in the job's history with who asked for it. accepted, job_id, number, position, and status: where the job now stands.",
        "tags": [
          "Job commands"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Any unique string. The same key with the same body returns the first answer and runs nothing twice.",
            "schema": {
              "type": "string"
            },
            "example": "6f1d2c9e-1b7a-4c1e-9a52-0d3f8e4b7a10"
          }
        ],
        "x-headhoncho": {
          "area": "jobs",
          "need": "do",
          "cost": "write",
          "mcp_tool": "change_job_status",
          "docs": "https://headhoncho.app/docs/api/change_job_status"
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "job_id": {
                    "type": "string",
                    "description": "The job id, or its number."
                  },
                  "status": {
                    "type": "string",
                    "description": "A status name or slug from Who am I. status_id works too."
                  },
                  "reason": {
                    "type": "string",
                    "description": "Read on a move into Canceled: why."
                  }
                },
                "required": [
                  "job_id",
                  "status"
                ]
              },
              "example": {
                "job_id": "job_1203",
                "status": "Dispatched",
                "reason": "Customer found another mover"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "accepted, job_id, number, position, and status: where the job now stands.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "accepted": true,
                  "job_id": "job_1203",
                  "number": 1203,
                  "position": 88411,
                  "status": {
                    "id": "st_03",
                    "name": "Dispatched",
                    "slug": "dispatched"
                  }
                }
              }
            }
          },
          "400": {
            "description": "missing_idempotency_key: Send an Idempotency-Key header on every command.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "missing_key: Send your key as Authorization: Bearer hh_live_... unknown_key: That key is not one we issued, or it was revoked. expired_token: That token has expired. Refresh it, or connect the app again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "no_access: This credential does not reach that. Ask the person who made it for access. read_only: This credential may read this area but not change it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found: No such record in the companies this credential reaches.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "idempotency_mismatch: This Idempotency-Key was used with a different body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid: Some fields were refused. See fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: Too many calls this minute. Wait and try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/commands/assign-worker": {
      "post": {
        "operationId": "assign_worker",
        "summary": "Assign worker",
        "description": "Put a technician or subcontractor on a job. If Job follow-up is on, the automation acts on it at once, as it would from the app. accepted, job_id, number, position, technicians now on the job, and automation: one sentence on what the automation did, or empty.",
        "tags": [
          "Job commands"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Any unique string. The same key with the same body returns the first answer and runs nothing twice.",
            "schema": {
              "type": "string"
            },
            "example": "6f1d2c9e-1b7a-4c1e-9a52-0d3f8e4b7a10"
          }
        ],
        "x-headhoncho": {
          "area": "jobs",
          "need": "do",
          "cost": "write",
          "mcp_tool": "assign_worker",
          "docs": "https://headhoncho.app/docs/api/assign_worker"
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "job_id": {
                    "type": "string",
                    "description": "The job id, or its number."
                  },
                  "worker_id": {
                    "type": "string",
                    "description": "From Who am I."
                  }
                },
                "required": [
                  "job_id",
                  "worker_id"
                ]
              },
              "example": {
                "job_id": "job_1203",
                "worker_id": "wrk_7a12"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "accepted, job_id, number, position, technicians now on the job, and automation: one sentence on what the automation did, or empty.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "accepted": true,
                  "job_id": "job_1203",
                  "number": 1203,
                  "position": 88411,
                  "technicians": [
                    {
                      "id": "wrk_7a12",
                      "name": "Omar S."
                    }
                  ],
                  "automation": "Ticket sent to Omar S."
                }
              }
            }
          },
          "400": {
            "description": "missing_idempotency_key: Send an Idempotency-Key header on every command.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "missing_key: Send your key as Authorization: Bearer hh_live_... unknown_key: That key is not one we issued, or it was revoked. expired_token: That token has expired. Refresh it, or connect the app again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "no_access: This credential does not reach that. Ask the person who made it for access. read_only: This credential may read this area but not change it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found: No such record in the companies this credential reaches.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "idempotency_mismatch: This Idempotency-Key was used with a different body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid: Some fields were refused. See fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: Too many calls this minute. Wait and try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/commands/unassign-worker": {
      "post": {
        "operationId": "unassign_worker",
        "summary": "Unassign worker",
        "description": "Take a technician or subcontractor off a job. The ticket's cancellation reaches them the way it does from the app. accepted, job_id, number, position, and technicians still on the job.",
        "tags": [
          "Job commands"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Any unique string. The same key with the same body returns the first answer and runs nothing twice.",
            "schema": {
              "type": "string"
            },
            "example": "6f1d2c9e-1b7a-4c1e-9a52-0d3f8e4b7a10"
          }
        ],
        "x-headhoncho": {
          "area": "jobs",
          "need": "do",
          "cost": "write",
          "mcp_tool": "unassign_worker",
          "docs": "https://headhoncho.app/docs/api/unassign_worker"
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "job_id": {
                    "type": "string",
                    "description": "The job id, or its number."
                  },
                  "worker_id": {
                    "type": "string",
                    "description": "From Who am I."
                  }
                },
                "required": [
                  "job_id",
                  "worker_id"
                ]
              },
              "example": {
                "job_id": "job_1203",
                "worker_id": "wrk_7a12"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "accepted, job_id, number, position, and technicians still on the job.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "accepted": true,
                  "job_id": "job_1203",
                  "number": 1203,
                  "position": 88411,
                  "technicians": []
                }
              }
            }
          },
          "400": {
            "description": "missing_idempotency_key: Send an Idempotency-Key header on every command.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "missing_key: Send your key as Authorization: Bearer hh_live_... unknown_key: That key is not one we issued, or it was revoked. expired_token: That token has expired. Refresh it, or connect the app again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "no_access: This credential does not reach that. Ask the person who made it for access. read_only: This credential may read this area but not change it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found: No such record in the companies this credential reaches.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "idempotency_mismatch: This Idempotency-Key was used with a different body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid: Some fields were refused. See fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: Too many calls this minute. Wait and try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/commands/add-job-note": {
      "post": {
        "operationId": "add_job_note",
        "summary": "Add job note",
        "description": "Add a note to a job. It appends to the job's log and never overwrites. accepted, job_id, number, note_id and position.",
        "tags": [
          "Job commands"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Any unique string. The same key with the same body returns the first answer and runs nothing twice.",
            "schema": {
              "type": "string"
            },
            "example": "6f1d2c9e-1b7a-4c1e-9a52-0d3f8e4b7a10"
          }
        ],
        "x-headhoncho": {
          "area": "jobs",
          "need": "do",
          "cost": "write",
          "mcp_tool": "add_job_note",
          "docs": "https://headhoncho.app/docs/api/add_job_note"
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "job_id": {
                    "type": "string",
                    "description": "The job id, or its number."
                  },
                  "body": {
                    "type": "string",
                    "description": "The note, as written."
                  }
                },
                "required": [
                  "job_id",
                  "body"
                ]
              },
              "example": {
                "job_id": "job_1203",
                "body": "Gate code 4410"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "accepted, job_id, number, note_id and position.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "accepted": true,
                  "job_id": "job_1203",
                  "number": 1203,
                  "position": 88411,
                  "note_id": "note_5c21"
                }
              }
            }
          },
          "400": {
            "description": "missing_idempotency_key: Send an Idempotency-Key header on every command.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "missing_key: Send your key as Authorization: Bearer hh_live_... unknown_key: That key is not one we issued, or it was revoked. expired_token: That token has expired. Refresh it, or connect the app again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "no_access: This credential does not reach that. Ask the person who made it for access. read_only: This credential may read this area but not change it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found: No such record in the companies this credential reaches.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "idempotency_mismatch: This Idempotency-Key was used with a different body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid: Some fields were refused. See fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: Too many calls this minute. Wait and try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/data/tables": {
      "get": {
        "operationId": "list_tables",
        "summary": "List tables",
        "description": "Every raw table, by the provider that produced it, with its columns, its grain and whether this company has it connected. schemas, one per provider (google_ads, meta_ads, microsoft_ads, callrail, headhoncho), each with its tables: key, name, grain, caveat, columns (key, label, kind), the date column if any, and available.",
        "tags": [
          "Raw data"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "company_id",
            "in": "query",
            "required": false,
            "description": "Default: the first company the credential reaches.",
            "schema": {
              "type": "string"
            },
            "example": "cmp_14f5"
          }
        ],
        "x-headhoncho": {
          "area": "raw",
          "need": "read",
          "cost": "read",
          "mcp_tool": "list_tables",
          "docs": "https://headhoncho.app/docs/api/list_tables"
        },
        "responses": {
          "200": {
            "description": "schemas, one per provider (google_ads, meta_ads, microsoft_ads, callrail, headhoncho), each with its tables: key, name, grain, caveat, columns (key, label, kind), the date column if any, and available.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "schemas": [
                    {
                      "key": "google_ads",
                      "label": "Google Ads",
                      "available": true,
                      "tables": [
                        {
                          "key": "google_ads.spend_by_day",
                          "name": "spend_by_day",
                          "grain": "One row per campaign per day.",
                          "date_column": "date",
                          "columns": [
                            {
                              "key": "date",
                              "label": "Date",
                              "kind": "date"
                            },
                            {
                              "key": "campaign_name",
                              "label": "Campaign",
                              "kind": "text"
                            },
                            {
                              "key": "cost",
                              "label": "Spend",
                              "kind": "money"
                            },
                            {
                              "key": "clicks",
                              "label": "Clicks",
                              "kind": "number"
                            }
                          ]
                        }
                      ]
                    },
                    {
                      "key": "callrail",
                      "label": "CallRail",
                      "available": true,
                      "tables": [
                        {
                          "key": "callrail.calls",
                          "name": "calls",
                          "grain": "One row per tracked call.",
                          "date_column": "started_at",
                          "columns": [
                            {
                              "key": "started_at",
                              "label": "Started",
                              "kind": "datetime"
                            },
                            {
                              "key": "customer_phone",
                              "label": "Caller",
                              "kind": "text"
                            },
                            {
                              "key": "has_identifier",
                              "label": "Click id",
                              "kind": "boolean"
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "missing_key: Send your key as Authorization: Bearer hh_live_... unknown_key: That key is not one we issued, or it was revoked. expired_token: That token has expired. Refresh it, or connect the app again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "no_access: This credential does not reach that. Ask the person who made it for access.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found: No such record in the companies this credential reaches. out_of_scope: No such company in the companies this credential reaches.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "not_connected: This company has not connected that provider yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid: Some fields were refused. See fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: Too many calls this minute. Wait and try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/data/tables/{key}": {
      "get": {
        "operationId": "read_table",
        "summary": "Read table",
        "description": "Rows of one raw table for one company and a date range, exactly as the provider reported them. No joins, no derived figures. table (its definition), rows, total, limit, offset, and the caveat the screen prints beside this table, because a row that leaves without its sentence will be misread.",
        "tags": [
          "Raw data"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "description": "A table key from List tables.",
            "schema": {
              "type": "string"
            },
            "example": "google_ads.spend_by_day"
          },
          {
            "name": "company_id",
            "in": "query",
            "required": false,
            "description": "Default: the first company the credential reaches.",
            "schema": {
              "type": "string"
            },
            "example": "cmp_14f5"
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "For tables with a date column. Default: this month.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-10-01"
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "The last day, inclusive.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-10-14"
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "A column key. Default: the table's own order.",
            "schema": {
              "type": "string"
            },
            "example": "date"
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "description": "asc or desc. One of asc, desc.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            },
            "example": "desc"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1 to 1000. Default 200.",
            "schema": {
              "type": "number"
            },
            "example": 200
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Rows to skip. Default 0.",
            "schema": {
              "type": "number"
            },
            "example": 0
          }
        ],
        "x-headhoncho": {
          "area": "raw",
          "need": "read",
          "cost": "read",
          "mcp_tool": "read_table",
          "docs": "https://headhoncho.app/docs/api/read_table"
        },
        "responses": {
          "200": {
            "description": "table (its definition), rows, total, limit, offset, and the caveat the screen prints beside this table, because a row that leaves without its sentence will be misread.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "table": {
                    "key": "google_ads.spend_by_day",
                    "grain": "One row per campaign per day.",
                    "caveat": "Spend is what Google reported for the day; it can move for 48 hours."
                  },
                  "rows": [
                    {
                      "date": "2026-10-13",
                      "campaign_name": "Moving - Bloomington",
                      "cost": 212.4,
                      "clicks": 41,
                      "impressions": 1904,
                      "currency": "USD"
                    },
                    {
                      "date": "2026-10-12",
                      "campaign_name": "Moving - Bloomington",
                      "cost": 198.05,
                      "clicks": 37,
                      "impressions": 1771,
                      "currency": "USD"
                    }
                  ],
                  "total": 28,
                  "limit": 200,
                  "offset": 0
                }
              }
            }
          },
          "401": {
            "description": "missing_key: Send your key as Authorization: Bearer hh_live_... unknown_key: That key is not one we issued, or it was revoked. expired_token: That token has expired. Refresh it, or connect the app again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "no_access: This credential does not reach that. Ask the person who made it for access.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found: No such record in the companies this credential reaches. out_of_scope: No such company in the companies this credential reaches.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "not_connected: This company has not connected that provider yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid: Some fields were refused. See fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: Too many calls this minute. Wait and try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/data/tables/{key}/export": {
      "get": {
        "operationId": "export_table",
        "summary": "Export table",
        "description": "One raw table as a CSV file for a company and a date range, up to 50,000 rows, with its caveat in the last lines. Over the API: the CSV itself (text/csv). Over MCP: a download link that works once for five minutes, so the file never passes through the AI.",
        "tags": [
          "Raw data"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "description": "A table key from List tables.",
            "schema": {
              "type": "string"
            },
            "example": "callrail.calls"
          },
          {
            "name": "company_id",
            "in": "query",
            "required": false,
            "description": "Default: the first company the credential reaches.",
            "schema": {
              "type": "string"
            },
            "example": "cmp_14f5"
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Default: this month.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-10-01"
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "The last day, inclusive.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-10-14"
          }
        ],
        "x-headhoncho": {
          "area": "raw",
          "need": "read",
          "cost": "export",
          "mcp_tool": "export_table",
          "docs": "https://headhoncho.app/docs/api/export_table"
        },
        "responses": {
          "200": {
            "description": "Over the API: the CSV itself (text/csv). Over MCP: a download link that works once for five minutes, so the file never passes through the AI.",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "missing_key: Send your key as Authorization: Bearer hh_live_... unknown_key: That key is not one we issued, or it was revoked. expired_token: That token has expired. Refresh it, or connect the app again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "no_access: This credential does not reach that. Ask the person who made it for access.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found: No such record in the companies this credential reaches. out_of_scope: No such company in the companies this credential reaches.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "not_connected: This company has not connected that provider yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid: Some fields were refused. See fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: Too many calls this minute. Wait and try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/jobs/{job_id}/conversion": {
      "get": {
        "operationId": "get_job_conversion",
        "summary": "Get job conversion",
        "description": "What this job told the ad platform it was worth: the click it came from, each conversion sent, its value and its basis, and what Google did with it. click: the network, the click id, the tracked call and its time, or null with why when the job came from no click. value_rules: how this company values a booked and a paid job. conversions: one per stage sent, with value, based_on, state (waiting, sent, accepted, refused, too_old, no_credits, taken_back) and at. total_sent is the sum Google accepted.",
        "tags": [
          "Click IDs and conversions"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "description": "The job id, or its number.",
            "schema": {
              "type": "string"
            },
            "example": "job_1138"
          }
        ],
        "x-headhoncho": {
          "area": "conv",
          "need": "read",
          "cost": "read",
          "mcp_tool": "get_job_conversion",
          "docs": "https://headhoncho.app/docs/api/get_job_conversion"
        },
        "responses": {
          "200": {
            "description": "click: the network, the click id, the tracked call and its time, or null with why when the job came from no click. value_rules: how this company values a booked and a paid job. conversions: one per stage sent, with value, based_on, state (waiting, sent, accepted, refused, too_old, no_credits, taken_back) and at. total_sent is the sum Google accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "job": {
                    "id": "job_1138",
                    "number": 1138
                  },
                  "click": {
                    "network": "google_ads",
                    "id": "Cj0KCQjw...",
                    "call_id": "CAL8f2e1",
                    "call_at": "2026-10-11T18:22:03Z",
                    "match": "matched"
                  },
                  "value_rules": {
                    "booked_job": "quoted",
                    "paid_job": "net_profit",
                    "paid_when": "first_payment"
                  },
                  "currency": "USD",
                  "conversions": [
                    {
                      "goal": "Booked job",
                      "stage": "booked",
                      "value": 1450.0,
                      "based_on": "the quoted price",
                      "state": "accepted",
                      "state_label": "Accepted by Google",
                      "at": "2026-10-12T06:00:00Z"
                    },
                    {
                      "goal": "Paid job",
                      "stage": "paid",
                      "value": 610.0,
                      "based_on": "profit after the subcontractor",
                      "state": "waiting",
                      "state_label": "Waiting for the first payment",
                      "at": null
                    }
                  ],
                  "total_sent": 1450.0
                }
              }
            }
          },
          "401": {
            "description": "missing_key: Send your key as Authorization: Bearer hh_live_... unknown_key: That key is not one we issued, or it was revoked. expired_token: That token has expired. Refresh it, or connect the app again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "no_access: This credential does not reach that. Ask the person who made it for access.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found: No such record in the companies this credential reaches.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid: Some fields were refused. See fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: Too many calls this minute. Wait and try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/reports/google-click-ids": {
      "get": {
        "operationId": "get_click_id_report",
        "summary": "Get click id report",
        "description": "The Google Click ID report: one row per job that opened or closed in the range, with its click id, its tracked call, its total and its profit. rows in the agency's fifteen columns, and totals: jobs, closed, with_click_id, with_call, total, company_total; plus calls, calls_with_click_id and calls_without_click_id for the range, because a call with no click id can never be in any file, whatever anyone does downstream.",
        "tags": [
          "Click IDs and conversions"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": true,
            "description": "The first day. A job is in the range when it opened or closed in it.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-10-01"
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "description": "The last day, inclusive.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-10-31"
          },
          {
            "name": "company_id",
            "in": "query",
            "required": false,
            "description": "Default: every company the credential reaches.",
            "schema": {
              "type": "string"
            },
            "example": "cmp_14f5"
          }
        ],
        "x-headhoncho": {
          "area": "conv",
          "need": "read",
          "cost": "read",
          "mcp_tool": "get_click_id_report",
          "docs": "https://headhoncho.app/docs/api/get_click_id_report"
        },
        "responses": {
          "200": {
            "description": "rows in the agency's fifteen columns, and totals: jobs, closed, with_click_id, with_call, total, company_total; plus calls, calls_with_click_id and calls_without_click_id for the range, because a call with no click id can never be in any file, whatever anyone does downstream.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "from": "2026-10-01",
                  "to": "2026-10-31",
                  "rows": [
                    {
                      "ticket": 1138,
                      "status": "Closed",
                      "customer_phone": "+12175550142",
                      "start_date": "2026-10-11 13:25",
                      "close_date": "2026-10-16 11:40",
                      "total": 1450.0,
                      "company_total": 610.0,
                      "job_source": "Google Ads",
                      "gclid": "Cj0KCQjw...",
                      "callrail_call_id": "CAL8f2e1",
                      "campaign_id": "2184930112",
                      "callrail_call_start": "2026-10-11 13:22",
                      "lead_source": "call",
                      "paid_date": "2026-10-16",
                      "time_zone": "America/Chicago"
                    }
                  ],
                  "totals": {
                    "jobs": 64,
                    "closed": 41,
                    "with_click_id": 37,
                    "with_call": 52,
                    "total": 71240.0,
                    "company_total": 28410.5
                  },
                  "calls": 118,
                  "calls_with_click_id": 69,
                  "calls_without_click_id": 49
                }
              }
            }
          },
          "401": {
            "description": "missing_key: Send your key as Authorization: Bearer hh_live_... unknown_key: That key is not one we issued, or it was revoked. expired_token: That token has expired. Refresh it, or connect the app again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "no_access: This credential does not reach that. Ask the person who made it for access.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found: No such record in the companies this credential reaches. out_of_scope: No such company in the companies this credential reaches.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid: Some fields were refused. See fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: Too many calls this minute. Wait and try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/reports/google-click-ids/export": {
      "get": {
        "operationId": "export_click_id_report",
        "summary": "Export click id report",
        "description": "The Google Click ID report as a file: the agency's sheet, or the Google Ads offline conversion upload file, ready to upload. Over the API: the file (text/csv). Over MCP: a download link that works once for five minutes.",
        "tags": [
          "Click IDs and conversions"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": true,
            "description": "The first day.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-10-01"
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "description": "The last day, inclusive.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-10-31"
          },
          {
            "name": "company_id",
            "in": "query",
            "required": false,
            "description": "For google_ads, the one account the file is for. Default: the first company.",
            "schema": {
              "type": "string"
            },
            "example": "cmp_14f5"
          },
          {
            "name": "output",
            "in": "query",
            "required": false,
            "description": "csv (the agency's fifteen columns, default) or google_ads (the upload file, one company at a time). One of csv, google_ads.",
            "schema": {
              "type": "string",
              "enum": [
                "csv",
                "google_ads"
              ]
            },
            "example": "csv"
          }
        ],
        "x-headhoncho": {
          "area": "conv",
          "need": "read",
          "cost": "export",
          "mcp_tool": "export_click_id_report",
          "docs": "https://headhoncho.app/docs/api/export_click_id_report"
        },
        "responses": {
          "200": {
            "description": "Over the API: the file (text/csv). Over MCP: a download link that works once for five minutes.",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "missing_key: Send your key as Authorization: Bearer hh_live_... unknown_key: That key is not one we issued, or it was revoked. expired_token: That token has expired. Refresh it, or connect the app again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "no_access: This credential does not reach that. Ask the person who made it for access.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found: No such record in the companies this credential reaches. out_of_scope: No such company in the companies this credential reaches.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "invalid: Some fields were refused. See fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: Too many calls this minute. Wait and try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Authorization: Bearer hh_live_.... Make a key at https://app.headhoncho.app/automations/api."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "missing_key",
                  "unknown_key",
                  "expired_token",
                  "no_access",
                  "read_only",
                  "not_found",
                  "out_of_scope",
                  "unknown_endpoint",
                  "unknown_command",
                  "invalid",
                  "missing_idempotency_key",
                  "idempotency_mismatch",
                  "rate_limited",
                  "not_connected"
                ]
              },
              "message": {
                "type": "string"
              },
              "fields": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              "docs": {
                "type": "string",
                "format": "uri"
              }
            },
            "required": [
              "code",
              "message"
            ]
          }
        },
        "required": [
          "error"
        ]
      }
    }
  }
}
