360 Secure SkillHub
360 安全技能中心

housecallpro-mcp

用纯 curl 读取 Housecall Pro 客户侧的报价单或发票,无需 MCP。输入为承包商发来的短链接(解析出令牌,报价 129 位、发票 32 位十六进制),输出 JSON,金额为整数美分、税率为小数。支持查看明细、状态与待办事项,可脚本化拒绝某报价选项(写操作令牌放 Authorization 头,读取放路径),但批准因需页面内 reCAPTCHA v3 令牌无法脚本化,须浏览器完成。适合脚本化取数或 MCP 未安装的场景;链接为一次性凭证,失效返回 401/403/404。

chrischall
chrischall
热度 59.22
最近更新 2026-10-10 23:30:40
分类:开发依赖:代码执行 / 第三方 API / 读写文件应用场景:API 集成 / 财务 / 数据处理

安装技能

复制下面的话术发给你的 Agent,它会通过 secure-skills 检索并安装该技能。

执行命令安装此 skill: npx secure-skills-cli add housecallpro-mcp
Metadata
name
housecallpro
description
Read a Housecall Pro estimate or invoice your contractor sent you — line items, totals, tax, what is still owed, the company behind it — from a shell with plain curl, instead of running the housecallpro-mcp server. Use when you want the data without the MCP, in a script, or on a machine where the MCP isn't installed. Covers declining an option, and why approving cannot be scripted.

Housecall Pro customer portal via curl (no MCP)

This reads the customer side of Housecall Pro: the estimate or invoice a contractor emails or texts you. It is not the Housecall Pro public API — that one serves the business running on Housecall Pro and needs an API key from their account.

No browser bridge is needed. app.housecallpro.com is not bot-walled; a plain curl gets a 200. Do not reach for fpx here.

The one thing to know first

There is no account and no standing link: Housecall Pro sends a separate, disposable link per document, so you work from whichever link you were sent.

Your link is a bearer credential. Anyone holding it can read the estimate and decline it. Keep it in a variable or a file you control, never in a command you paste into a shared transcript, and never commit it.

Setup

Two forms of link exist, and estimates and invoices use different token shapes: an estimate token is 129 characters (two 64-char hex halves joined by _), an invoice token is a bare 32-char hex string. The short link redirects to whichever applies:

# What your contractor sent (short form)
SHORT='https://pro.housecallpro.com/mobile_estimate/XXXXXXXXXX'   # or /mobile_invoice/…

# Resolve it to the retrieval token (an estimate's is 129 chars, an invoice's 32)
HCP_TOKEN=$(curl -sI "$SHORT" | tr -d '\r' | awk 'tolower($1)=="location:"{print $2}' | sed 's#.*/##')

# If you already have a client.housecallpro.com/estimates/… or /invoices/… link, just:
# HCP_TOKEN='<the last path segment>'

echo "${#HCP_TOKEN}"   # 129 for an estimate, 32 for an invoice

Read the estimate

curl -s -H 'Accept: application/json' \
  "https://app.housecallpro.com/alpha/customer_estimates/$HCP_TOKEN" > estimate.json

No Authorization header — reads carry the token in the path.

Money is integer cents. total_amount: 34639 means $346.39. Divide by 100 before reporting anything. tax.rate is the exception: it is a fraction (0.0825 = 8.25%) and must not be scaled.

A one-line summary:

jq -r '
  "\(.company_name) — estimate #\(.estimate.data.estimate_number)",
  "For: \(.customer_name) at \(.estimate.data.address.data.printable_address)",
  (.options.data[] |
    "  \(.name) [\(.status)] $\(.total_amount/100)"),
  (.options.data[].line_items.data[] |
    "    \(.quantity) x \(.name) @ $\(.unit_price/100) = $\(.amount/100)")
' estimate.json

More recipes, including the company lookup and the full field map, are in references/recipes.md.

Is it still awaiting me?

jq -r '.options.data[] | select(.approval_date == null and .status != "Declined")
       | "OPEN: \(.name) $\(.total_amount/100)"' estimate.json

Declining an option

Declining works from a shell. It is the only mutation that does — and it tells the contractor you are not proceeding, so read the estimate first.

OPTION_ID=$(jq -r '.options.data[0].id' estimate.json)   # est_…

curl -s -X POST \
  -H "Authorization: Token $HCP_TOKEN" \
  --data-urlencode "estimate_option_uuids[]=$OPTION_ID" \
  https://app.housecallpro.com/api/estimates/estimate_options/customer_declines

Note the asymmetry: writes put the token in the Authorization header, reads put it in the path.

A 2xx is not proof. Re-read and check the option actually moved:

curl -s "https://app.housecallpro.com/alpha/customer_estimates/$HCP_TOKEN" \
  | jq -r '.options.data[] | "\(.id) \(.status) approval_date=\(.approval_date)"'

Approving cannot be scripted

POST /api/estimates/estimate_options/customer_approvals requires a response_token — a reCAPTCHA v3 token minted in-page for the action estimates_customer_approvals. No shell client can produce one, and neither can the fetchproxy bridge (it issues fetch calls; it does not run page JS).

Open the link in a browser and press Approve there. Do not try to work around this: approving is a binding commitment to a quoted price.

Failure modes

Symptom Meaning
401 / 403 The link expired or was revoked. Ask your contractor to resend it.
404 (HTML) The document was deleted or re-issued; the old link is dead.
$HCP_TOKEN is empty or not 129 chars The short link expired before it could redirect.
A 200 that isn't JSON Same as above — the link is no longer valid.

Invoices

curl -s -H 'Accept: application/json' \
  "https://app.housecallpro.com/api/invoices/consumer/v1/invoices/$HCP_TOKEN" > invoice.json

The v1 segment is required. /api/invoices/consumer/invoices/<token> — the same path without it — returns 404, as do the /api/v2/consumer/invoices/… and /api/v2/consumer/sent_invoices/… paths that also appear in the app's JavaScript. Only this one answers.

jq -r '"\(.company_info.name) — invoice #\(.invoice_number) [\(.status)]",
       "  subtotal $\(.subtotal/100)",
       "  tax      $\((.total - .subtotal)/100)",
       "  total    $\(.total/100)",
       "  due      $\(.due_amount/100)"' invoice.json

Two things to know: the invoice document has no line items (a paid invoice renders as a summary in the portal, and the API returns exactly that), and no tax field — the tax figure is total - subtotal. Money is integer cents here too. Use due_amount rather than status to decide whether it is settled.