{"id":"338426f1-b255-47ed-a5fb-a5b29d46b2c3","entityType":"agent","slug":"clawhub-chrischall-housecallpro-mcp","name":"housecallpro-mcp","canonicalUrl":"https://www.xpersona.co/agent/clawhub-chrischall-housecallpro-mcp","canonicalPath":"/agent/clawhub-chrischall-housecallpro-mcp","generatedAt":"2026-10-11T20:59:17.588Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"editorial-content","verified":true,"confidence":"high","updatedAt":"2026-10-11T18:26:57.801Z","emptyReason":null},"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. Skill: housecallpro-mcp Owner: chrischall Summary: 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 cann","descriptionLabel":"Technical summary","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 1K downloads reported by the source. Last updated 10/11/2026.","installCommand":"clawhub skill install s17cjx1a349nz5apaqp02vgz4h85728z:housecallpro-mcp","sourceUrl":"https://clawhub.ai/chrischall/housecallpro-mcp","homepage":"https://clawhub.ai/chrischall/skills/housecallpro-mcp","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/chrischall/housecallpro-mcp","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/chrischall/skills/housecallpro-mcp","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":60,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"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"},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-11T18:26:57.801Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T18:26:57.801Z","emptyReason":null},"stars":null,"forks":null,"downloads":1009,"packageName":null,"latestVersion":"1.2.2","tractionLabel":"1K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T18:26:57.728Z","emptyReason":null},"lastUpdatedAt":"2026-10-11T18:26:57.801Z","lastCrawledAt":"2026-10-11T18:26:57.728Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-12T18:26:57.728Z","lastVerifiedAt":null,"highlights":[{"version":"1.2.2","createdAt":"2026-10-10T15:29:19.740Z","changelog":"- Removed the sample skill-card.md file. - No changes to core functionality or usage instructions.","fileCount":4,"zipByteSize":6702},{"version":"1.2.1","createdAt":"2026-10-09T23:24:19.218Z","changelog":"- Removed the sample file skill-card.md. - No changes to logic or functionality; documentation and usage remain unchanged.","fileCount":4,"zipByteSize":6755},{"version":"1.2.0","createdAt":"2026-10-07T13:38:31.416Z","changelog":"- Removed redundant skill-card.md file. - No changes to functionality or user-facing documentation. - Version bump to 1.2.0 for housekeeping.","fileCount":4,"zipByteSize":6701},{"version":"1.1.2","createdAt":"2026-10-05T02:49:35.536Z","changelog":"- Removed the file skill-card.md. - No other feature or documentation changes in this release.","fileCount":4,"zipByteSize":6663},{"version":"1.1.1","createdAt":"2026-10-03T01:40:31.459Z","changelog":"- Removed the file skill-card.md. - No changes to skill behavior or documentation outside of file removal.","fileCount":4,"zipByteSize":6508},{"version":"1.1.0","createdAt":"2026-09-24T15:11:45.174Z","changelog":"- Removed the file skill-card.md. - No changes to functionality or documentation in SKILL.md. - No other files were affected in this version.","fileCount":4,"zipByteSize":6817},{"version":"1.0.3","createdAt":"2026-09-23T21:39:33.791Z","changelog":"- Removed the file skill-card.md. - No user-facing changes to commands or functionality. - Documentation and usage remain unchanged.","fileCount":4,"zipByteSize":6831},{"version":"1.0.2","createdAt":"2026-09-23T15:58:37.704Z","changelog":"- Removed the file skill-card.md. - No changes made to the SKILL.md documentation or functionality.","fileCount":4,"zipByteSize":6894}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s17cjx1a349nz5apaqp02vgz4h85728z:housecallpro-mcp","setupComplexity":"low","setupSteps":["Setup complexity is classified as HIGH. You must provision dedicated cloud infrastructure or an isolated VM. Do not run this directly on your local workstation.","Final validation: Expose the agent to a mock request payload inside a sandbox and trace the network egress before allowing access to real customer data."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-housecallpro-mcp/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-housecallpro-mcp/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-housecallpro-mcp/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-housecallpro-mcp/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-housecallpro-mcp/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-housecallpro-mcp/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-11T20:59:17.584Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-housecallpro-mcp/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-housecallpro-mcp/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-housecallpro-mcp/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-housecallpro-mcp/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"high","updatedAt":"2026-10-11T18:26:57.801Z","emptyReason":null},"readme":"Skill: housecallpro-mcp\n\nOwner: chrischall\n\nSummary: 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.\n\nTags: latest:1.2.2\n\nVersion history:\n\nv1.2.2 | 2026-10-10T15:29:19.740Z | auto\n\n- Removed the sample skill-card.md file.\n- No changes to core functionality or usage instructions.\n\nv1.2.1 | 2026-10-09T23:24:19.218Z | auto\n\n- Removed the sample file skill-card.md.\n- No changes to logic or functionality; documentation and usage remain unchanged.\n\nv1.2.0 | 2026-10-07T13:38:31.416Z | auto\n\n- Removed redundant skill-card.md file.\n- No changes to functionality or user-facing documentation.\n- Version bump to 1.2.0 for housekeeping.\n\nv1.1.2 | 2026-10-05T02:49:35.536Z | auto\n\n- Removed the file skill-card.md.\n- No other feature or documentation changes in this release.\n\nv1.1.1 | 2026-10-03T01:40:31.459Z | auto\n\n- Removed the file skill-card.md.\n- No changes to skill behavior or documentation outside of file removal.\n\nv1.1.0 | 2026-09-24T15:11:45.174Z | auto\n\n- Removed the file skill-card.md.\n- No changes to functionality or documentation in SKILL.md.\n- No other files were affected in this version.\n\nv1.0.3 | 2026-09-23T21:39:33.791Z | auto\n\n- Removed the file skill-card.md.\n- No user-facing changes to commands or functionality.\n- Documentation and usage remain unchanged.\n\nv1.0.2 | 2026-09-23T15:58:37.704Z | auto\n\n- Removed the file skill-card.md.\n- No changes made to the SKILL.md documentation or functionality.\n\nv1.0.1 | 2026-09-21T04:13:29.869Z | auto\n\n- Removed sample file: skill-card.md\n- No changes to functionality or documentation.\n\nv1.0.0 | 2026-09-20T02:50:01.269Z | auto\n\n- Removed the file skill-card.md.\n- No other user-facing changes in functionality or documentation.\n\nv0.4.0 | 2026-09-17T23:36:03.117Z | auto\n\n- Removed the sample file: skill-card.md.\n- No changes to functionality or user-facing documentation.\n\nv0.3.1 | 2026-09-10T17:50:00.623Z | auto\n\n- Removed the file: skill-card.md\n- No changes to functionality or documentation in SKILL.md\n- Maintenance cleanup: removal of an unused or auxiliary documentation file\n\nv0.3.0 | 2026-09-04T22:21:00.622Z | auto\n\n- Removed the sample file skill-card.md.\n- No functional or documentation changes to the skill itself.\n\nv0.2.0 | 2026-08-15T13:40:19.404Z | auto\n\n- SKILL.md updated: clarified that there is no account or standing link, and each Housecall Pro document uses a unique, disposable link.\n- Removed mention of the now-deleted skill-card.md.\n- No code or API changes; documentation improvement only.\n\nv0.1.0 | 2026-08-15T03:23:28.953Z | auto\n\nInitial release: Query Housecall Pro customer estimates and invoices via curl, no MCP server required.\n\n- Read estimate and invoice details sent by contractors directly from the command line using curl.\n- No browser bridge or MCP installation needed.\n- Supports reading line items, totals, tax, and outstanding balances on estimates and invoices.\n- Allows declining estimate options via API; approvals require browser interaction for reCAPTCHA.\n- Documents token handling, data extraction with jq, and common error scenarios.\n\nArchive index:\n\nArchive v1.2.2: 4 files, 6702 bytes\n\nFiles: references/recipes.md (5775b), skill-card.md (2108b), SKILL.md (5867b), _meta.json (135b)\n\nFile v1.2.2:SKILL.md\n\n---\nname: housecallpro\ndescription: >-\n  Read a Housecall Pro estimate or invoice your contractor sent you — line\n  items, totals, tax, what is still owed, the company behind it — from a shell\n  with plain curl, instead of running the housecallpro-mcp server. Use when you want the data without the MCP, in a\n  script, or on a machine where the MCP isn't installed. Covers declining an\n  option, and why approving cannot be scripted.\n---\n\n# Housecall Pro customer portal via curl (no MCP)\n\nThis reads the **customer** side of Housecall Pro: the estimate or invoice a\ncontractor emails or texts you. It is not the Housecall Pro public API — that\none serves the business running on Housecall Pro and needs an API key from\ntheir account.\n\n**No browser bridge is needed.** `app.housecallpro.com` is not bot-walled; a\nplain `curl` gets a `200`. Do not reach for `fpx` here.\n\n## The one thing to know first\n\nThere is no account and no standing link: Housecall Pro sends a separate,\ndisposable link per document, so you work from whichever link you were sent.\n\nYour link is a **bearer credential**. Anyone holding it can read the estimate\nand decline it. Keep it in a variable or a file you control, never in a command\nyou paste into a shared transcript, and never commit it.\n\n## Setup\n\nTwo forms of link exist, and **estimates and invoices use different token\nshapes**: an estimate token is 129 characters (two 64-char hex halves joined by\n`_`), an invoice token is a bare 32-char hex string. The short link redirects to\nwhichever applies:\n\n```sh\n# What your contractor sent (short form)\nSHORT='https://pro.housecallpro.com/mobile_estimate/XXXXXXXXXX'   # or /mobile_invoice/…\n\n# Resolve it to the retrieval token (an estimate's is 129 chars, an invoice's 32)\nHCP_TOKEN=$(curl -sI \"$SHORT\" | tr -d '\\r' | awk 'tolower($1)==\"location:\"{print $2}' | sed 's#.*/##')\n\n# If you already have a client.housecallpro.com/estimates/… or /invoices/… link, just:\n# HCP_TOKEN='<the last path segment>'\n\necho \"${#HCP_TOKEN}\"   # 129 for an estimate, 32 for an invoice\n```\n\n## Read the estimate\n\n```sh\ncurl -s -H 'Accept: application/json' \\\n  \"https://app.housecallpro.com/alpha/customer_estimates/$HCP_TOKEN\" > estimate.json\n```\n\nNo `Authorization` header — reads carry the token in the path.\n\n**Money is integer cents.** `total_amount: 34639` means `$346.39`. Divide by 100\nbefore reporting anything. `tax.rate` is the exception: it is a fraction\n(`0.0825` = 8.25%) and must not be scaled.\n\nA one-line summary:\n\n```sh\njq -r '\n  \"\\(.company_name) — estimate #\\(.estimate.data.estimate_number)\",\n  \"For: \\(.customer_name) at \\(.estimate.data.address.data.printable_address)\",\n  (.options.data[] |\n    \"  \\(.name) [\\(.status)] $\\(.total_amount/100)\"),\n  (.options.data[].line_items.data[] |\n    \"    \\(.quantity) x \\(.name) @ $\\(.unit_price/100) = $\\(.amount/100)\")\n' estimate.json\n```\n\nMore recipes, including the company lookup and the full field map, are in\n[`references/recipes.md`](references/recipes.md).\n\n## Is it still awaiting me?\n\n```sh\njq -r '.options.data[] | select(.approval_date == null and .status != \"Declined\")\n       | \"OPEN: \\(.name) $\\(.total_amount/100)\"' estimate.json\n```\n\n## Declining an option\n\nDeclining works from a shell. It is the only mutation that does — and it tells\nthe contractor you are not proceeding, so read the estimate first.\n\n```sh\nOPTION_ID=$(jq -r '.options.data[0].id' estimate.json)   # est_…\n\ncurl -s -X POST \\\n  -H \"Authorization: Token $HCP_TOKEN\" \\\n  --data-urlencode \"estimate_option_uuids[]=$OPTION_ID\" \\\n  https://app.housecallpro.com/api/estimates/estimate_options/customer_declines\n```\n\nNote the asymmetry: **writes put the token in the `Authorization` header**,\nreads put it in the path.\n\n**A 2xx is not proof.** Re-read and check the option actually moved:\n\n```sh\ncurl -s \"https://app.housecallpro.com/alpha/customer_estimates/$HCP_TOKEN\" \\\n  | jq -r '.options.data[] | \"\\(.id) \\(.status) approval_date=\\(.approval_date)\"'\n```\n\n## Approving cannot be scripted\n\n`POST /api/estimates/estimate_options/customer_approvals` requires a\n`response_token` — a **reCAPTCHA v3 token** minted in-page for the action\n`estimates_customer_approvals`. No shell client can produce one, and neither can\nthe fetchproxy bridge (it issues `fetch` calls; it does not run page JS).\n\nOpen the link in a browser and press Approve there. Do not try to work around\nthis: approving is a binding commitment to a quoted price.\n\n## Failure modes\n\n| Symptom | Meaning |\n| --- | --- |\n| `401` / `403` | The link expired or was revoked. Ask your contractor to resend it. |\n| `404` (HTML) | The document was deleted or re-issued; the old link is dead. |\n| `$HCP_TOKEN` is empty or not 129 chars | The short link expired before it could redirect. |\n| A `200` that isn't JSON | Same as above — the link is no longer valid. |\n\n## Invoices\n\n```sh\ncurl -s -H 'Accept: application/json' \\\n  \"https://app.housecallpro.com/api/invoices/consumer/v1/invoices/$HCP_TOKEN\" > invoice.json\n```\n\n**The `v1` segment is required.** `/api/invoices/consumer/invoices/<token>` —\nthe same path without it — returns 404, as do the `/api/v2/consumer/invoices/…`\nand `/api/v2/consumer/sent_invoices/…` paths that also appear in the app's\nJavaScript. Only this one answers.\n\n```sh\njq -r '\"\\(.company_info.name) — invoice #\\(.invoice_number) [\\(.status)]\",\n       \"  subtotal $\\(.subtotal/100)\",\n       \"  tax      $\\((.total - .subtotal)/100)\",\n       \"  total    $\\(.total/100)\",\n       \"  due      $\\(.due_amount/100)\"' invoice.json\n```\n\nTwo things to know: the invoice document has **no line items** (a paid invoice\nrenders as a summary in the portal, and the API returns exactly that), and **no\ntax field** — the tax figure is `total - subtotal`. Money is integer cents here\ntoo. Use `due_amount` rather than `status` to decide whether it is settled.\n\nFile v1.2.2:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"housecallpro-mcp\",\n  \"version\": \"1.2.2\",\n  \"publishedAt\": 1791646159740\n}\n\nFile v1.2.2:references/recipes.md\n\n# Housecall Pro consumer API — recipes\n\nEvery recipe assumes `HCP_TOKEN` holds the 129-character retrieval token, as set\nup in `SKILL.md`. Endpoints marked **unverified** were read out of the SPA's\nJavaScript but never exercised against a real document.\n\n## Field map — `GET /alpha/customer_estimates/$HCP_TOKEN` (verified)\n\nThe envelope nests under `{object, data}` wrappers.\n\n| Path | What it is |\n| --- | --- |\n| `.estimate.data.estimate_number` | Human-facing estimate number |\n| `.estimate.data.estimate_uuid` | `csr_…`, the estimate's id |\n| `.estimate.data.organization_id` | Company UUID — feeds the company lookup |\n| `.estimate.data.customer_approval_mode` | e.g. `single_option` |\n| `.estimate.data.address.data.printable_address` | Service address |\n| `.options.data[]` | One entry per priced option |\n| `.options.data[].id` | `est_…` — **this is what you decline** |\n| `.options.data[].status` | e.g. `Awaiting Approval`, `Declined` |\n| `.options.data[].approval_date` | `null` until approved |\n| `.options.data[].sub_total` / `.total_amount` | **integer cents** |\n| `.options.data[].tax.data.rate` | Fraction (`0.0825`), NOT cents |\n| `.options.data[].tax.data.amount` | **integer cents** |\n| `.options.data[].line_items.data[]` | `name`, `description`, `quantity`, `kind`, `unit_price`, `amount` |\n| `.customer_name`, `.customer_email` | Who it is for |\n| `.message_from_pro` | Free-text note |\n| `.company_name`, `.company_phone_number`, `.company_email`, `.company_website` | The contractor |\n| `.payment_options.can_pay_online` | Whether online payment is offered |\n| `.deposit_requirement` | e.g. `not required` |\n| `.signatures_enabled` | Whether approval demands a signature |\n\n`kind` on a line item is `labor` or `material` — not `service`.\n\n## Totals, correctly scaled\n\n```sh\njq -r '.options.data[]\n  | \"\\(.name): subtotal $\\(.sub_total/100) + tax $\\(.tax.data.amount/100)\"\n  + \" (\\(.tax.data.rate*100)%) = $\\(.total_amount/100)\"' estimate.json\n```\n\n## Line items as TSV\n\n```sh\njq -r '.options.data[].line_items.data[]\n  | [.name, .kind, .quantity, (.unit_price/100), (.amount/100)] | @tsv' estimate.json\n```\n\n## Just the open options and their ids\n\n```sh\njq -r '.options.data[]\n  | select(.approval_date == null and .status != \"Declined\")\n  | \"\\(.id)\\t\\(.name)\\t$\\(.total_amount/100)\"' estimate.json\n```\n\n## The contractor behind it (verified)\n\n```sh\nORG=$(jq -r '.estimate.data.organization_id' estimate.json)\ncurl -s \"https://app.housecallpro.com/alpha/organizations/$ORG\" | jq\n```\n\nReturns `id`, `company_name`, `phone_number`, `email`, `website`, `logo_url`,\n`address`, `default_arrival_window`, `terms_url`, `founding_pro_uuid`. Needs no\nauth header at all.\n\n## Decline (verified shape, from the app's own code)\n\n```sh\ncurl -s -X POST \\\n  -H \"Authorization: Token $HCP_TOKEN\" \\\n  --data-urlencode \"estimate_option_uuids[]=est_XXXX\" \\\n  https://app.housecallpro.com/api/estimates/estimate_options/customer_declines\n```\n\nRepeat `--data-urlencode \"estimate_option_uuids[]=…\"` per option.\n\n## Approve — blocked\n\n```\nPOST /api/estimates/estimate_options/customer_approvals\n  Authorization: Token <token>\n  estimate_option_uuids[]=est_…\n  response_token=<reCAPTCHA v3 token>       <-- cannot be produced outside the page\n  signature[signature], signature[signatory_name], signature[signatory_user_agent]\n```\n\n`estimates_customer_approvals` is the only reCAPTCHA action in the entire\nconsumer app. Approve in a browser.\n\n## Invoices — `GET /api/invoices/consumer/v1/invoices/$HCP_TOKEN` (verified)\n\nAn invoice token is **32 hex characters**, not the estimate's 129. The document\nis flat — no `{object, data}` wrappers.\n\n| Path | What it is |\n| --- | --- |\n| `.invoice_number` | Human-facing invoice number |\n| `.status` | Display string, e.g. `paid` |\n| `.due_amount` | **integer cents** — the field that decides paid-ness |\n| `.subtotal` / `.total` / `.amount` | **integer cents** |\n| `.company_info.{name,phone_number,email,website,organization_uuid}` | The contractor |\n| `.customer.{uuid,email,mobile_number,card_on_file}` | You |\n| `.payment_options.can_pay_online` | Whether it can still be paid online |\n| `.invoice_count` | How many invoices the job carries |\n\nNo line items, and no tax field — tax is `total - subtotal`.\n\n```sh\njq -r '\"#\\(.invoice_number) [\\(.status)] due $\\(.due_amount/100) of $\\(.total/100)\"' invoice.json\n```\n\nThese 404 for an invoice token despite appearing in the bundles — do not use\nthem: `/api/invoices/consumer/invoices/{t}`, `/api/v2/consumer/invoices/{t}`,\n`/api/v2/consumer/sent_invoices/{t}`,\n`/api/invoices/linking/consumer/sources/{t}`,\n`/api/v2/consumer/invoices/{t}/invoice_or_estimate_pdf`.\n`/alpha/jobs/{t}` answers 401 — it wants a different credential.\n\n## Other endpoints (unverified)\n\nRead out of the SPA bundles; shapes unconfirmed.\n\n**Account-level portal (OTP login, spans all documents from one pro)** —\n`/api/customer_portal/request_otp`, `/api/customer_portal/verify_otp`,\n`/api/v2/customer_portal/magic_links`, `/api/v2/customer_portal/organizations`,\n`/api/v2/consumer/user/{log_in,log_out,profile,service_agreements}`\n\n**Jobs / media** — `/alpha/jobs/{id}`, `/api/customer_gallery/{id}`,\n`/api/attachments/customer_gallery/{id}`, `/alpha/after_actions/{id}`\n\n**Service agreements** — `/api/consumer/service_agreement/{id}`,\n`/api/v2/consumer/service_agreement/{id}`\n\n**Communications** — `/communications/consumer/organizations`,\n`/communications/consumer/preferences/{id}`,\n`/communications/consumer/consents/{opt_in,opt_out}`\n\n## Safety\n\n- The retrieval token is a bearer credential. Never commit it, never paste it\n  into a shared transcript, never put it in a URL you log.\n- Declining is not reversible from here.\n- Never attempt to script approval.\n\nFile v1.2.2:skill-card.md\n\n## Description:\n\nGuides customers in reading Housecall Pro estimates and invoices from the shell and declining estimate options without an MCP server.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nCustomers and developers use this skill to inspect contractor estimates, line items, invoice totals, and amounts due with shell commands. It also explains how to decline an estimate option and why approvals must happen in a browser.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Customer links grant access to the document and can authorize a decline if disclosed.\n\nMitigation: Keep links and tokens out of shared logs, transcripts, and repositories; store them only in a controlled variable or file.\n\nRisk: Declining an option can notify the contractor and may not be reversible from the shell.\n\nMitigation: Review the estimate and selected option before declining, then re-read the document to confirm its status.\n\nRisk: An approval commits the customer to a quoted price and cannot be scripted with this skill.\n\nMitigation: Complete approvals in the browser; do not attempt to bypass the approval flow.\n\n## Reference(s):\n\n- [Housecall Pro consumer API recipes](artifact/references/recipes.md)\n- [ClawHub skill release](https://clawhub.ai/chrischall/skills/housecallpro-mcp)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Guidance]\n\n**Output Format:** [Markdown with shell and jq examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Summaries of estimates or invoices may include customer and contractor details; amounts are reported in currency units rather than integer cents.]\n\n## Skill Version(s):\n\n1.2.2 (source: ClawHub release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.2.1: 4 files, 6755 bytes\n\nFiles: references/recipes.md (5775b), skill-card.md (2233b), SKILL.md (5867b), _meta.json (135b)\n\nFile v1.2.1:SKILL.md\n\n---\nname: housecallpro\ndescription: >-\n  Read a Housecall Pro estimate or invoice your contractor sent you — line\n  items, totals, tax, what is still owed, the company behind it — from a shell\n  with plain curl, instead of running the housecallpro-mcp server. Use when you want the data without the MCP, in a\n  script, or on a machine where the MCP isn't installed. Covers declining an\n  option, and why approving cannot be scripted.\n---\n\n# Housecall Pro customer portal via curl (no MCP)\n\nThis reads the **customer** side of Housecall Pro: the estimate or invoice a\ncontractor emails or texts you. It is not the Housecall Pro public API — that\none serves the business running on Housecall Pro and needs an API key from\ntheir account.\n\n**No browser bridge is needed.** `app.housecallpro.com` is not bot-walled; a\nplain `curl` gets a `200`. Do not reach for `fpx` here.\n\n## The one thing to know first\n\nThere is no account and no standing link: Housecall Pro sends a separate,\ndisposable link per document, so you work from whichever link you were sent.\n\nYour link is a **bearer credential**. Anyone holding it can read the estimate\nand decline it. Keep it in a variable or a file you control, never in a command\nyou paste into a shared transcript, and never commit it.\n\n## Setup\n\nTwo forms of link exist, and **estimates and invoices use different token\nshapes**: an estimate token is 129 characters (two 64-char hex halves joined by\n`_`), an invoice token is a bare 32-char hex string. The short link redirects to\nwhichever applies:\n\n```sh\n# What your contractor sent (short form)\nSHORT='https://pro.housecallpro.com/mobile_estimate/XXXXXXXXXX'   # or /mobile_invoice/…\n\n# Resolve it to the retrieval token (an estimate's is 129 chars, an invoice's 32)\nHCP_TOKEN=$(curl -sI \"$SHORT\" | tr -d '\\r' | awk 'tolower($1)==\"location:\"{print $2}' | sed 's#.*/##')\n\n# If you already have a client.housecallpro.com/estimates/… or /invoices/… link, just:\n# HCP_TOKEN='<the last path segment>'\n\necho \"${#HCP_TOKEN}\"   # 129 for an estimate, 32 for an invoice\n```\n\n## Read the estimate\n\n```sh\ncurl -s -H 'Accept: application/json' \\\n  \"https://app.housecallpro.com/alpha/customer_estimates/$HCP_TOKEN\" > estimate.json\n```\n\nNo `Authorization` header — reads carry the token in the path.\n\n**Money is integer cents.** `total_amount: 34639` means `$346.39`. Divide by 100\nbefore reporting anything. `tax.rate` is the exception: it is a fraction\n(`0.0825` = 8.25%) and must not be scaled.\n\nA one-line summary:\n\n```sh\njq -r '\n  \"\\(.company_name) — estimate #\\(.estimate.data.estimate_number)\",\n  \"For: \\(.customer_name) at \\(.estimate.data.address.data.printable_address)\",\n  (.options.data[] |\n    \"  \\(.name) [\\(.status)] $\\(.total_amount/100)\"),\n  (.options.data[].line_items.data[] |\n    \"    \\(.quantity) x \\(.name) @ $\\(.unit_price/100) = $\\(.amount/100)\")\n' estimate.json\n```\n\nMore recipes, including the company lookup and the full field map, are in\n[`references/recipes.md`](references/recipes.md).\n\n## Is it still awaiting me?\n\n```sh\njq -r '.options.data[] | select(.approval_date == null and .status != \"Declined\")\n       | \"OPEN: \\(.name) $\\(.total_amount/100)\"' estimate.json\n```\n\n## Declining an option\n\nDeclining works from a shell. It is the only mutation that does — and it tells\nthe contractor you are not proceeding, so read the estimate first.\n\n```sh\nOPTION_ID=$(jq -r '.options.data[0].id' estimate.json)   # est_…\n\ncurl -s -X POST \\\n  -H \"Authorization: Token $HCP_TOKEN\" \\\n  --data-urlencode \"estimate_option_uuids[]=$OPTION_ID\" \\\n  https://app.housecallpro.com/api/estimates/estimate_options/customer_declines\n```\n\nNote the asymmetry: **writes put the token in the `Authorization` header**,\nreads put it in the path.\n\n**A 2xx is not proof.** Re-read and check the option actually moved:\n\n```sh\ncurl -s \"https://app.housecallpro.com/alpha/customer_estimates/$HCP_TOKEN\" \\\n  | jq -r '.options.data[] | \"\\(.id) \\(.status) approval_date=\\(.approval_date)\"'\n```\n\n## Approving cannot be scripted\n\n`POST /api/estimates/estimate_options/customer_approvals` requires a\n`response_token` — a **reCAPTCHA v3 token** minted in-page for the action\n`estimates_customer_approvals`. No shell client can produce one, and neither can\nthe fetchproxy bridge (it issues `fetch` calls; it does not run page JS).\n\nOpen the link in a browser and press Approve there. Do not try to work around\nthis: approving is a binding commitment to a quoted price.\n\n## Failure modes\n\n| Symptom | Meaning |\n| --- | --- |\n| `401` / `403` | The link expired or was revoked. Ask your contractor to resend it. |\n| `404` (HTML) | The document was deleted or re-issued; the old link is dead. |\n| `$HCP_TOKEN` is empty or not 129 chars | The short link expired before it could redirect. |\n| A `200` that isn't JSON | Same as above — the link is no longer valid. |\n\n## Invoices\n\n```sh\ncurl -s -H 'Accept: application/json' \\\n  \"https://app.housecallpro.com/api/invoices/consumer/v1/invoices/$HCP_TOKEN\" > invoice.json\n```\n\n**The `v1` segment is required.** `/api/invoices/consumer/invoices/<token>` —\nthe same path without it — returns 404, as do the `/api/v2/consumer/invoices/…`\nand `/api/v2/consumer/sent_invoices/…` paths that also appear in the app's\nJavaScript. Only this one answers.\n\n```sh\njq -r '\"\\(.company_info.name) — invoice #\\(.invoice_number) [\\(.status)]\",\n       \"  subtotal $\\(.subtotal/100)\",\n       \"  tax      $\\((.total - .subtotal)/100)\",\n       \"  total    $\\(.total/100)\",\n       \"  due      $\\(.due_amount/100)\"' invoice.json\n```\n\nTwo things to know: the invoice document has **no line items** (a paid invoice\nrenders as a summary in the portal, and the API returns exactly that), and **no\ntax field** — the tax figure is `total - subtotal`. Money is integer cents here\ntoo. Use `due_amount` rather than `status` to decide whether it is settled.\n\nFile v1.2.1:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"housecallpro-mcp\",\n  \"version\": \"1.2.1\",\n  \"publishedAt\": 1791588259218\n}\n\nFile v1.2.1:references/recipes.md\n\n# Housecall Pro consumer API — recipes\n\nEvery recipe assumes `HCP_TOKEN` holds the 129-character retrieval token, as set\nup in `SKILL.md`. Endpoints marked **unverified** were read out of the SPA's\nJavaScript but never exercised against a real document.\n\n## Field map — `GET /alpha/customer_estimates/$HCP_TOKEN` (verified)\n\nThe envelope nests under `{object, data}` wrappers.\n\n| Path | What it is |\n| --- | --- |\n| `.estimate.data.estimate_number` | Human-facing estimate number |\n| `.estimate.data.estimate_uuid` | `csr_…`, the estimate's id |\n| `.estimate.data.organization_id` | Company UUID — feeds the company lookup |\n| `.estimate.data.customer_approval_mode` | e.g. `single_option` |\n| `.estimate.data.address.data.printable_address` | Service address |\n| `.options.data[]` | One entry per priced option |\n| `.options.data[].id` | `est_…` — **this is what you decline** |\n| `.options.data[].status` | e.g. `Awaiting Approval`, `Declined` |\n| `.options.data[].approval_date` | `null` until approved |\n| `.options.data[].sub_total` / `.total_amount` | **integer cents** |\n| `.options.data[].tax.data.rate` | Fraction (`0.0825`), NOT cents |\n| `.options.data[].tax.data.amount` | **integer cents** |\n| `.options.data[].line_items.data[]` | `name`, `description`, `quantity`, `kind`, `unit_price`, `amount` |\n| `.customer_name`, `.customer_email` | Who it is for |\n| `.message_from_pro` | Free-text note |\n| `.company_name`, `.company_phone_number`, `.company_email`, `.company_website` | The contractor |\n| `.payment_options.can_pay_online` | Whether online payment is offered |\n| `.deposit_requirement` | e.g. `not required` |\n| `.signatures_enabled` | Whether approval demands a signature |\n\n`kind` on a line item is `labor` or `material` — not `service`.\n\n## Totals, correctly scaled\n\n```sh\njq -r '.options.data[]\n  | \"\\(.name): subtotal $\\(.sub_total/100) + tax $\\(.tax.data.amount/100)\"\n  + \" (\\(.tax.data.rate*100)%) = $\\(.total_amount/100)\"' estimate.json\n```\n\n## Line items as TSV\n\n```sh\njq -r '.options.data[].line_items.data[]\n  | [.name, .kind, .quantity, (.unit_price/100), (.amount/100)] | @tsv' estimate.json\n```\n\n## Just the open options and their ids\n\n```sh\njq -r '.options.data[]\n  | select(.approval_date == null and .status != \"Declined\")\n  | \"\\(.id)\\t\\(.name)\\t$\\(.total_amount/100)\"' estimate.json\n```\n\n## The contractor behind it (verified)\n\n```sh\nORG=$(jq -r '.estimate.data.organization_id' estimate.json)\ncurl -s \"https://app.housecallpro.com/alpha/organizations/$ORG\" | jq\n```\n\nReturns `id`, `company_name`, `phone_number`, `email`, `website`, `logo_url`,\n`address`, `default_arrival_window`, `terms_url`, `founding_pro_uuid`. Needs no\nauth header at all.\n\n## Decline (verified shape, from the app's own code)\n\n```sh\ncurl -s -X POST \\\n  -H \"Authorization: Token $HCP_TOKEN\" \\\n  --data-urlencode \"estimate_option_uuids[]=est_XXXX\" \\\n  https://app.housecallpro.com/api/estimates/estimate_options/customer_declines\n```\n\nRepeat `--data-urlencode \"estimate_option_uuids[]=…\"` per option.\n\n## Approve — blocked\n\n```\nPOST /api/estimates/estimate_options/customer_approvals\n  Authorization: Token <token>\n  estimate_option_uuids[]=est_…\n  response_token=<reCAPTCHA v3 token>       <-- cannot be produced outside the page\n  signature[signature], signature[signatory_name], signature[signatory_user_agent]\n```\n\n`estimates_customer_approvals` is the only reCAPTCHA action in the entire\nconsumer app. Approve in a browser.\n\n## Invoices — `GET /api/invoices/consumer/v1/invoices/$HCP_TOKEN` (verified)\n\nAn invoice token is **32 hex characters**, not the estimate's 129. The document\nis flat — no `{object, data}` wrappers.\n\n| Path | What it is |\n| --- | --- |\n| `.invoice_number` | Human-facing invoice number |\n| `.status` | Display string, e.g. `paid` |\n| `.due_amount` | **integer cents** — the field that decides paid-ness |\n| `.subtotal` / `.total` / `.amount` | **integer cents** |\n| `.company_info.{name,phone_number,email,website,organization_uuid}` | The contractor |\n| `.customer.{uuid,email,mobile_number,card_on_file}` | You |\n| `.payment_options.can_pay_online` | Whether it can still be paid online |\n| `.invoice_count` | How many invoices the job carries |\n\nNo line items, and no tax field — tax is `total - subtotal`.\n\n```sh\njq -r '\"#\\(.invoice_number) [\\(.status)] due $\\(.due_amount/100) of $\\(.total/100)\"' invoice.json\n```\n\nThese 404 for an invoice token despite appearing in the bundles — do not use\nthem: `/api/invoices/consumer/invoices/{t}`, `/api/v2/consumer/invoices/{t}`,\n`/api/v2/consumer/sent_invoices/{t}`,\n`/api/invoices/linking/consumer/sources/{t}`,\n`/api/v2/consumer/invoices/{t}/invoice_or_estimate_pdf`.\n`/alpha/jobs/{t}` answers 401 — it wants a different credential.\n\n## Other endpoints (unverified)\n\nRead out of the SPA bundles; shapes unconfirmed.\n\n**Account-level portal (OTP login, spans all documents from one pro)** —\n`/api/customer_portal/request_otp`, `/api/customer_portal/verify_otp`,\n`/api/v2/customer_portal/magic_links`, `/api/v2/customer_portal/organizations`,\n`/api/v2/consumer/user/{log_in,log_out,profile,service_agreements}`\n\n**Jobs / media** — `/alpha/jobs/{id}`, `/api/customer_gallery/{id}`,\n`/api/attachments/customer_gallery/{id}`, `/alpha/after_actions/{id}`\n\n**Service agreements** — `/api/consumer/service_agreement/{id}`,\n`/api/v2/consumer/service_agreement/{id}`\n\n**Communications** — `/communications/consumer/organizations`,\n`/communications/consumer/preferences/{id}`,\n`/communications/consumer/consents/{opt_in,opt_out}`\n\n## Safety\n\n- The retrieval token is a bearer credential. Never commit it, never paste it\n  into a shared transcript, never put it in a URL you log.\n- Declining is not reversible from here.\n- Never attempt to script approval.\n\nFile v1.2.1:skill-card.md\n\n## Description:\n\nGuides agents in reading Housecall Pro customer estimates and invoices with shell commands and explains how to decline an estimate option or approve it in a browser.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nCustomers and their agents use a contractor-provided link to review estimate options, prices, contractor details, and invoice balances without running an MCP server. They may also review how to decline a selected estimate option.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Document links act as bearer credentials and may expose estimates or allow a decline if shared.\n\nMitigation: Keep links and tokens out of shared transcripts, logs, and repositories; handle them only in controlled files or variables.\n\nRisk: Declining the wrong estimate option can notify the contractor and cannot be reversed through this workflow.\n\nMitigation: List open options, confirm the exact option ID and details before declining, then reread the estimate to verify its status.\n\nRisk: Unverified account, OTP, profile, service agreement, jobs, media, and communications endpoints extend beyond the core document-reading task.\n\nMitigation: Avoid those endpoints unless separately reviewed and explicitly needed; complete approvals in the browser rather than attempting to script them.\n\n## Reference(s):\n\n- [Housecall Pro consumer API recipes](references/recipes.md)\n- [ClawHub skill listing](https://clawhub.ai/chrischall/skills/housecallpro-mcp)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Guidance]\n\n**Output Format:** [Markdown with shell and jq examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Explains estimate and invoice fields, amount scaling, and limits on scripted approval.]\n\n## Skill Version(s):\n\n1.2.1 (source: ClawHub release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.2.0: 4 files, 6701 bytes\n\nFiles: references/recipes.md (5775b), skill-card.md (2157b), SKILL.md (5867b), _meta.json (135b)\n\nFile v1.2.0:SKILL.md\n\n---\nname: housecallpro\ndescription: >-\n  Read a Housecall Pro estimate or invoice your contractor sent you — line\n  items, totals, tax, what is still owed, the company behind it — from a shell\n  with plain curl, instead of running the housecallpro-mcp server. Use when you want the data without the MCP, in a\n  script, or on a machine where the MCP isn't installed. Covers declining an\n  option, and why approving cannot be scripted.\n---\n\n# Housecall Pro customer portal via curl (no MCP)\n\nThis reads the **customer** side of Housecall Pro: the estimate or invoice a\ncontractor emails or texts you. It is not the Housecall Pro public API — that\none serves the business running on Housecall Pro and needs an API key from\ntheir account.\n\n**No browser bridge is needed.** `app.housecallpro.com` is not bot-walled; a\nplain `curl` gets a `200`. Do not reach for `fpx` here.\n\n## The one thing to know first\n\nThere is no account and no standing link: Housecall Pro sends a separate,\ndisposable link per document, so you work from whichever link you were sent.\n\nYour link is a **bearer credential**. Anyone holding it can read the estimate\nand decline it. Keep it in a variable or a file you control, never in a command\nyou paste into a shared transcript, and never commit it.\n\n## Setup\n\nTwo forms of link exist, and **estimates and invoices use different token\nshapes**: an estimate token is 129 characters (two 64-char hex halves joined by\n`_`), an invoice token is a bare 32-char hex string. The short link redirects to\nwhichever applies:\n\n```sh\n# What your contractor sent (short form)\nSHORT='https://pro.housecallpro.com/mobile_estimate/XXXXXXXXXX'   # or /mobile_invoice/…\n\n# Resolve it to the retrieval token (an estimate's is 129 chars, an invoice's 32)\nHCP_TOKEN=$(curl -sI \"$SHORT\" | tr -d '\\r' | awk 'tolower($1)==\"location:\"{print $2}' | sed 's#.*/##')\n\n# If you already have a client.housecallpro.com/estimates/… or /invoices/… link, just:\n# HCP_TOKEN='<the last path segment>'\n\necho \"${#HCP_TOKEN}\"   # 129 for an estimate, 32 for an invoice\n```\n\n## Read the estimate\n\n```sh\ncurl -s -H 'Accept: application/json' \\\n  \"https://app.housecallpro.com/alpha/customer_estimates/$HCP_TOKEN\" > estimate.json\n```\n\nNo `Authorization` header — reads carry the token in the path.\n\n**Money is integer cents.** `total_amount: 34639` means `$346.39`. Divide by 100\nbefore reporting anything. `tax.rate` is the exception: it is a fraction\n(`0.0825` = 8.25%) and must not be scaled.\n\nA one-line summary:\n\n```sh\njq -r '\n  \"\\(.company_name) — estimate #\\(.estimate.data.estimate_number)\",\n  \"For: \\(.customer_name) at \\(.estimate.data.address.data.printable_address)\",\n  (.options.data[] |\n    \"  \\(.name) [\\(.status)] $\\(.total_amount/100)\"),\n  (.options.data[].line_items.data[] |\n    \"    \\(.quantity) x \\(.name) @ $\\(.unit_price/100) = $\\(.amount/100)\")\n' estimate.json\n```\n\nMore recipes, including the company lookup and the full field map, are in\n[`references/recipes.md`](references/recipes.md).\n\n## Is it still awaiting me?\n\n```sh\njq -r '.options.data[] | select(.approval_date == null and .status != \"Declined\")\n       | \"OPEN: \\(.name) $\\(.total_amount/100)\"' estimate.json\n```\n\n## Declining an option\n\nDeclining works from a shell. It is the only mutation that does — and it tells\nthe contractor you are not proceeding, so read the estimate first.\n\n```sh\nOPTION_ID=$(jq -r '.options.data[0].id' estimate.json)   # est_…\n\ncurl -s -X POST \\\n  -H \"Authorization: Token $HCP_TOKEN\" \\\n  --data-urlencode \"estimate_option_uuids[]=$OPTION_ID\" \\\n  https://app.housecallpro.com/api/estimates/estimate_options/customer_declines\n```\n\nNote the asymmetry: **writes put the token in the `Authorization` header**,\nreads put it in the path.\n\n**A 2xx is not proof.** Re-read and check the option actually moved:\n\n```sh\ncurl -s \"https://app.housecallpro.com/alpha/customer_estimates/$HCP_TOKEN\" \\\n  | jq -r '.options.data[] | \"\\(.id) \\(.status) approval_date=\\(.approval_date)\"'\n```\n\n## Approving cannot be scripted\n\n`POST /api/estimates/estimate_options/customer_approvals` requires a\n`response_token` — a **reCAPTCHA v3 token** minted in-page for the action\n`estimates_customer_approvals`. No shell client can produce one, and neither can\nthe fetchproxy bridge (it issues `fetch` calls; it does not run page JS).\n\nOpen the link in a browser and press Approve there. Do not try to work around\nthis: approving is a binding commitment to a quoted price.\n\n## Failure modes\n\n| Symptom | Meaning |\n| --- | --- |\n| `401` / `403` | The link expired or was revoked. Ask your contractor to resend it. |\n| `404` (HTML) | The document was deleted or re-issued; the old link is dead. |\n| `$HCP_TOKEN` is empty or not 129 chars | The short link expired before it could redirect. |\n| A `200` that isn't JSON | Same as above — the link is no longer valid. |\n\n## Invoices\n\n```sh\ncurl -s -H 'Accept: application/json' \\\n  \"https://app.housecallpro.com/api/invoices/consumer/v1/invoices/$HCP_TOKEN\" > invoice.json\n```\n\n**The `v1` segment is required.** `/api/invoices/consumer/invoices/<token>` —\nthe same path without it — returns 404, as do the `/api/v2/consumer/invoices/…`\nand `/api/v2/consumer/sent_invoices/…` paths that also appear in the app's\nJavaScript. Only this one answers.\n\n```sh\njq -r '\"\\(.company_info.name) — invoice #\\(.invoice_number) [\\(.status)]\",\n       \"  subtotal $\\(.subtotal/100)\",\n       \"  tax      $\\((.total - .subtotal)/100)\",\n       \"  total    $\\(.total/100)\",\n       \"  due      $\\(.due_amount/100)\"' invoice.json\n```\n\nTwo things to know: the invoice document has **no line items** (a paid invoice\nrenders as a summary in the portal, and the API returns exactly that), and **no\ntax field** — the tax figure is `total - subtotal`. Money is integer cents here\ntoo. Use `due_amount` rather than `status` to decide whether it is settled.\n\nFile v1.2.0:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"housecallpro-mcp\",\n  \"version\": \"1.2.0\",\n  \"publishedAt\": 1791380311416\n}\n\nFile v1.2.0:references/recipes.md\n\n# Housecall Pro consumer API — recipes\n\nEvery recipe assumes `HCP_TOKEN` holds the 129-character retrieval token, as set\nup in `SKILL.md`. Endpoints marked **unverified** were read out of the SPA's\nJavaScript but never exercised against a real document.\n\n## Field map — `GET /alpha/customer_estimates/$HCP_TOKEN` (verified)\n\nThe envelope nests under `{object, data}` wrappers.\n\n| Path | What it is |\n| --- | --- |\n| `.estimate.data.estimate_number` | Human-facing estimate number |\n| `.estimate.data.estimate_uuid` | `csr_…`, the estimate's id |\n| `.estimate.data.organization_id` | Company UUID — feeds the company lookup |\n| `.estimate.data.customer_approval_mode` | e.g. `single_option` |\n| `.estimate.data.address.data.printable_address` | Service address |\n| `.options.data[]` | One entry per priced option |\n| `.options.data[].id` | `est_…` — **this is what you decline** |\n| `.options.data[].status` | e.g. `Awaiting Approval`, `Declined` |\n| `.options.data[].approval_date` | `null` until approved |\n| `.options.data[].sub_total` / `.total_amount` | **integer cents** |\n| `.options.data[].tax.data.rate` | Fraction (`0.0825`), NOT cents |\n| `.options.data[].tax.data.amount` | **integer cents** |\n| `.options.data[].line_items.data[]` | `name`, `description`, `quantity`, `kind`, `unit_price`, `amount` |\n| `.customer_name`, `.customer_email` | Who it is for |\n| `.message_from_pro` | Free-text note |\n| `.company_name`, `.company_phone_number`, `.company_email`, `.company_website` | The contractor |\n| `.payment_options.can_pay_online` | Whether online payment is offered |\n| `.deposit_requirement` | e.g. `not required` |\n| `.signatures_enabled` | Whether approval demands a signature |\n\n`kind` on a line item is `labor` or `material` — not `service`.\n\n## Totals, correctly scaled\n\n```sh\njq -r '.options.data[]\n  | \"\\(.name): subtotal $\\(.sub_total/100) + tax $\\(.tax.data.amount/100)\"\n  + \" (\\(.tax.data.rate*100)%) = $\\(.total_amount/100)\"' estimate.json\n```\n\n## Line items as TSV\n\n```sh\njq -r '.options.data[].line_items.data[]\n  | [.name, .kind, .quantity, (.unit_price/100), (.amount/100)] | @tsv' estimate.json\n```\n\n## Just the open options and their ids\n\n```sh\njq -r '.options.data[]\n  | select(.approval_date == null and .status != \"Declined\")\n  | \"\\(.id)\\t\\(.name)\\t$\\(.total_amount/100)\"' estimate.json\n```\n\n## The contractor behind it (verified)\n\n```sh\nORG=$(jq -r '.estimate.data.organization_id' estimate.json)\ncurl -s \"https://app.housecallpro.com/alpha/organizations/$ORG\" | jq\n```\n\nReturns `id`, `company_name`, `phone_number`, `email`, `website`, `logo_url`,\n`address`, `default_arrival_window`, `terms_url`, `founding_pro_uuid`. Needs no\nauth header at all.\n\n## Decline (verified shape, from the app's own code)\n\n```sh\ncurl -s -X POST \\\n  -H \"Authorization: Token $HCP_TOKEN\" \\\n  --data-urlencode \"estimate_option_uuids[]=est_XXXX\" \\\n  https://app.housecallpro.com/api/estimates/estimate_options/customer_declines\n```\n\nRepeat `--data-urlencode \"estimate_option_uuids[]=…\"` per option.\n\n## Approve — blocked\n\n```\nPOST /api/estimates/estimate_options/customer_approvals\n  Authorization: Token <token>\n  estimate_option_uuids[]=est_…\n  response_token=<reCAPTCHA v3 token>       <-- cannot be produced outside the page\n  signature[signature], signature[signatory_name], signature[signatory_user_agent]\n```\n\n`estimates_customer_approvals` is the only reCAPTCHA action in the entire\nconsumer app. Approve in a browser.\n\n## Invoices — `GET /api/invoices/consumer/v1/invoices/$HCP_TOKEN` (verified)\n\nAn invoice token is **32 hex characters**, not the estimate's 129. The document\nis flat — no `{object, data}` wrappers.\n\n| Path | What it is |\n| --- | --- |\n| `.invoice_number` | Human-facing invoice number |\n| `.status` | Display string, e.g. `paid` |\n| `.due_amount` | **integer cents** — the field that decides paid-ness |\n| `.subtotal` / `.total` / `.amount` | **integer cents** |\n| `.company_info.{name,phone_number,email,website,organization_uuid}` | The contractor |\n| `.customer.{uuid,email,mobile_number,card_on_file}` | You |\n| `.payment_options.can_pay_online` | Whether it can still be paid online |\n| `.invoice_count` | How many invoices the job carries |\n\nNo line items, and no tax field — tax is `total - subtotal`.\n\n```sh\njq -r '\"#\\(.invoice_number) [\\(.status)] due $\\(.due_amount/100) of $\\(.total/100)\"' invoice.json\n```\n\nThese 404 for an invoice token despite appearing in the bundles — do not use\nthem: `/api/invoices/consumer/invoices/{t}`, `/api/v2/consumer/invoices/{t}`,\n`/api/v2/consumer/sent_invoices/{t}`,\n`/api/invoices/linking/consumer/sources/{t}`,\n`/api/v2/consumer/invoices/{t}/invoice_or_estimate_pdf`.\n`/alpha/jobs/{t}` answers 401 — it wants a different credential.\n\n## Other endpoints (unverified)\n\nRead out of the SPA bundles; shapes unconfirmed.\n\n**Account-level portal (OTP login, spans all documents from one pro)** —\n`/api/customer_portal/request_otp`, `/api/customer_portal/verify_otp`,\n`/api/v2/customer_portal/magic_links`, `/api/v2/customer_portal/organizations`,\n`/api/v2/consumer/user/{log_in,log_out,profile,service_agreements}`\n\n**Jobs / media** — `/alpha/jobs/{id}`, `/api/customer_gallery/{id}`,\n`/api/attachments/customer_gallery/{id}`, `/alpha/after_actions/{id}`\n\n**Service agreements** — `/api/consumer/service_agreement/{id}`,\n`/api/v2/consumer/service_agreement/{id}`\n\n**Communications** — `/communications/consumer/organizations`,\n`/communications/consumer/preferences/{id}`,\n`/communications/consumer/consents/{opt_in,opt_out}`\n\n## Safety\n\n- The retrieval token is a bearer credential. Never commit it, never paste it\n  into a shared transcript, never put it in a URL you log.\n- Declining is not reversible from here.\n- Never attempt to script approval.\n\nFile v1.2.0:skill-card.md\n\n## Description:\n\nGuides customers in reading Housecall Pro estimates and invoices with shell commands, checking what is owed, and declining estimate options without an MCP server.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nCustomers and developers use this skill to inspect contractor-sent estimates and invoices, summarize charges and amounts due, and optionally decline an estimate option from a shell.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Housecall Pro document links function as bearer credentials and can expose customer documents or allow an option to be declined.\n\nMitigation: Keep links and tokens out of shared transcripts, logs, and repositories; use only links you are authorized to access.\n\nRisk: The reference material lists unsupported account, media, service-agreement, and communications endpoints beyond the customer-document task.\n\nMitigation: Avoid those unsupported endpoints unless you have a clear authorized reason to use them.\n\nRisk: Declining an estimate option notifies the contractor and cannot be reversed through this skill.\n\nMitigation: Review the estimate and selected option before declining, then re-read the document to confirm its status.\n\n## Reference(s):\n\n- [ClawHub skill release](https://clawhub.ai/chrischall/skills/housecallpro-mcp)\n- [Housecall Pro consumer API recipes](artifact/references/recipes.md)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Guidance]\n\n**Output Format:** [Markdown with shell examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Provides document summaries and instructions for verifying a declined option; approval must be completed in a browser.]\n\n## Skill Version(s):\n\n1.2.0 (source: ClawHub release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.1.2: 4 files, 6663 bytes\n\nFiles: references/recipes.md (5775b), skill-card.md (2088b), SKILL.md (5867b), _meta.json (135b)\n\nFile v1.1.2:SKILL.md\n\n---\nname: housecallpro\ndescription: >-\n  Read a Housecall Pro estimate or invoice your contractor sent you — line\n  items, totals, tax, what is still owed, the company behind it — from a shell\n  with plain curl, instead of running the housecallpro-mcp server. Use when you want the data without the MCP, in a\n  script, or on a machine where the MCP isn't installed. Covers declining an\n  option, and why approving cannot be scripted.\n---\n\n# Housecall Pro customer portal via curl (no MCP)\n\nThis reads the **customer** side of Housecall Pro: the estimate or invoice a\ncontractor emails or texts you. It is not the Housecall Pro public API — that\none serves the business running on Housecall Pro and needs an API key from\ntheir account.\n\n**No browser bridge is needed.** `app.housecallpro.com` is not bot-walled; a\nplain `curl` gets a `200`. Do not reach for `fpx` here.\n\n## The one thing to know first\n\nThere is no account and no standing link: Housecall Pro sends a separate,\ndisposable link per document, so you work from whichever link you were sent.\n\nYour link is a **bearer credential**. Anyone holding it can read the estimate\nand decline it. Keep it in a variable or a file you control, never in a command\nyou paste into a shared transcript, and never commit it.\n\n## Setup\n\nTwo forms of link exist, and **estimates and invoices use different token\nshapes**: an estimate token is 129 characters (two 64-char hex halves joined by\n`_`), an invoice token is a bare 32-char hex string. The short link redirects to\nwhichever applies:\n\n```sh\n# What your contractor sent (short form)\nSHORT='https://pro.housecallpro.com/mobile_estimate/XXXXXXXXXX'   # or /mobile_invoice/…\n\n# Resolve it to the retrieval token (an estimate's is 129 chars, an invoice's 32)\nHCP_TOKEN=$(curl -sI \"$SHORT\" | tr -d '\\r' | awk 'tolower($1)==\"location:\"{print $2}' | sed 's#.*/##')\n\n# If you already have a client.housecallpro.com/estimates/… or /invoices/… link, just:\n# HCP_TOKEN='<the last path segment>'\n\necho \"${#HCP_TOKEN}\"   # 129 for an estimate, 32 for an invoice\n```\n\n## Read the estimate\n\n```sh\ncurl -s -H 'Accept: application/json' \\\n  \"https://app.housecallpro.com/alpha/customer_estimates/$HCP_TOKEN\" > estimate.json\n```\n\nNo `Authorization` header — reads carry the token in the path.\n\n**Money is integer cents.** `total_amount: 34639` means `$346.39`. Divide by 100\nbefore reporting anything. `tax.rate` is the exception: it is a fraction\n(`0.0825` = 8.25%) and must not be scaled.\n\nA one-line summary:\n\n```sh\njq -r '\n  \"\\(.company_name) — estimate #\\(.estimate.data.estimate_number)\",\n  \"For: \\(.customer_name) at \\(.estimate.data.address.data.printable_address)\",\n  (.options.data[] |\n    \"  \\(.name) [\\(.status)] $\\(.total_amount/100)\"),\n  (.options.data[].line_items.data[] |\n    \"    \\(.quantity) x \\(.name) @ $\\(.unit_price/100) = $\\(.amount/100)\")\n' estimate.json\n```\n\nMore recipes, including the company lookup and the full field map, are in\n[`references/recipes.md`](references/recipes.md).\n\n## Is it still awaiting me?\n\n```sh\njq -r '.options.data[] | select(.approval_date == null and .status != \"Declined\")\n       | \"OPEN: \\(.name) $\\(.total_amount/100)\"' estimate.json\n```\n\n## Declining an option\n\nDeclining works from a shell. It is the only mutation that does — and it tells\nthe contractor you are not proceeding, so read the estimate first.\n\n```sh\nOPTION_ID=$(jq -r '.options.data[0].id' estimate.json)   # est_…\n\ncurl -s -X POST \\\n  -H \"Authorization: Token $HCP_TOKEN\" \\\n  --data-urlencode \"estimate_option_uuids[]=$OPTION_ID\" \\\n  https://app.housecallpro.com/api/estimates/estimate_options/customer_declines\n```\n\nNote the asymmetry: **writes put the token in the `Authorization` header**,\nreads put it in the path.\n\n**A 2xx is not proof.** Re-read and check the option actually moved:\n\n```sh\ncurl -s \"https://app.housecallpro.com/alpha/customer_estimates/$HCP_TOKEN\" \\\n  | jq -r '.options.data[] | \"\\(.id) \\(.status) approval_date=\\(.approval_date)\"'\n```\n\n## Approving cannot be scripted\n\n`POST /api/estimates/estimate_options/customer_approvals` requires a\n`response_token` — a **reCAPTCHA v3 token** minted in-page for the action\n`estimates_customer_approvals`. No shell client can produce one, and neither can\nthe fetchproxy bridge (it issues `fetch` calls; it does not run page JS).\n\nOpen the link in a browser and press Approve there. Do not try to work around\nthis: approving is a binding commitment to a quoted price.\n\n## Failure modes\n\n| Symptom | Meaning |\n| --- | --- |\n| `401` / `403` | The link expired or was revoked. Ask your contractor to resend it. |\n| `404` (HTML) | The document was deleted or re-issued; the old link is dead. |\n| `$HCP_TOKEN` is empty or not 129 chars | The short link expired before it could redirect. |\n| A `200` that isn't JSON | Same as above — the link is no longer valid. |\n\n## Invoices\n\n```sh\ncurl -s -H 'Accept: application/json' \\\n  \"https://app.housecallpro.com/api/invoices/consumer/v1/invoices/$HCP_TOKEN\" > invoice.json\n```\n\n**The `v1` segment is required.** `/api/invoices/consumer/invoices/<token>` —\nthe same path without it — returns 404, as do the `/api/v2/consumer/invoices/…`\nand `/api/v2/consumer/sent_invoices/…` paths that also appear in the app's\nJavaScript. Only this one answers.\n\n```sh\njq -r '\"\\(.company_info.name) — invoice #\\(.invoice_number) [\\(.status)]\",\n       \"  subtotal $\\(.subtotal/100)\",\n       \"  tax      $\\((.total - .subtotal)/100)\",\n       \"  total    $\\(.total/100)\",\n       \"  due      $\\(.due_amount/100)\"' invoice.json\n```\n\nTwo things to know: the invoice document has **no line items** (a paid invoice\nrenders as a summary in the portal, and the API returns exactly that), and **no\ntax field** — the tax figure is `total - subtotal`. Money is integer cents here\ntoo. Use `due_amount` rather than `status` to decide whether it is settled.\n\nFile v1.1.2:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"housecallpro-mcp\",\n  \"version\": \"1.1.2\",\n  \"publishedAt\": 1791168575536\n}\n\nFile v1.1.2:references/recipes.md\n\n# Housecall Pro consumer API — recipes\n\nEvery recipe assumes `HCP_TOKEN` holds the 129-character retrieval token, as set\nup in `SKILL.md`. Endpoints marked **unverified** were read out of the SPA's\nJavaScript but never exercised against a real document.\n\n## Field map — `GET /alpha/customer_estimates/$HCP_TOKEN` (verified)\n\nThe envelope nests under `{object, data}` wrappers.\n\n| Path | What it is |\n| --- | --- |\n| `.estimate.data.estimate_number` | Human-facing estimate number |\n| `.estimate.data.estimate_uuid` | `csr_…`, the estimate's id |\n| `.estimate.data.organization_id` | Company UUID — feeds the company lookup |\n| `.estimate.data.customer_approval_mode` | e.g. `single_option` |\n| `.estimate.data.address.data.printable_address` | Service address |\n| `.options.data[]` | One entry per priced option |\n| `.options.data[].id` | `est_…` — **this is what you decline** |\n| `.options.data[].status` | e.g. `Awaiting Approval`, `Declined` |\n| `.options.data[].approval_date` | `null` until approved |\n| `.options.data[].sub_total` / `.total_amount` | **integer cents** |\n| `.options.data[].tax.data.rate` | Fraction (`0.0825`), NOT cents |\n| `.options.data[].tax.data.amount` | **integer cents** |\n| `.options.data[].line_items.data[]` | `name`, `description`, `quantity`, `kind`, `unit_price`, `amount` |\n| `.customer_name`, `.customer_email` | Who it is for |\n| `.message_from_pro` | Free-text note |\n| `.company_name`, `.company_phone_number`, `.company_email`, `.company_website` | The contractor |\n| `.payment_options.can_pay_online` | Whether online payment is offered |\n| `.deposit_requirement` | e.g. `not required` |\n| `.signatures_enabled` | Whether approval demands a signature |\n\n`kind` on a line item is `labor` or `material` — not `service`.\n\n## Totals, correctly scaled\n\n```sh\njq -r '.options.data[]\n  | \"\\(.name): subtotal $\\(.sub_total/100) + tax $\\(.tax.data.amount/100)\"\n  + \" (\\(.tax.data.rate*100)%) = $\\(.total_amount/100)\"' estimate.json\n```\n\n## Line items as TSV\n\n```sh\njq -r '.options.data[].line_items.data[]\n  | [.name, .kind, .quantity, (.unit_price/100), (.amount/100)] | @tsv' estimate.json\n```\n\n## Just the open options and their ids\n\n```sh\njq -r '.options.data[]\n  | select(.approval_date == null and .status != \"Declined\")\n  | \"\\(.id)\\t\\(.name)\\t$\\(.total_amount/100)\"' estimate.json\n```\n\n## The contractor behind it (verified)\n\n```sh\nORG=$(jq -r '.estimate.data.organization_id' estimate.json)\ncurl -s \"https://app.housecallpro.com/alpha/organizations/$ORG\" | jq\n```\n\nReturns `id`, `company_name`, `phone_number`, `email`, `website`, `logo_url`,\n`address`, `default_arrival_window`, `terms_url`, `founding_pro_uuid`. Needs no\nauth header at all.\n\n## Decline (verified shape, from the app's own code)\n\n```sh\ncurl -s -X POST \\\n  -H \"Authorization: Token $HCP_TOKEN\" \\\n  --data-urlencode \"estimate_option_uuids[]=est_XXXX\" \\\n  https://app.housecallpro.com/api/estimates/estimate_options/customer_declines\n```\n\nRepeat `--data-urlencode \"estimate_option_uuids[]=…\"` per option.\n\n## Approve — blocked\n\n```\nPOST /api/estimates/estimate_options/customer_approvals\n  Authorization: Token <token>\n  estimate_option_uuids[]=est_…\n  response_token=<reCAPTCHA v3 token>       <-- cannot be produced outside the page\n  signature[signature], signature[signatory_name], signature[signatory_user_agent]\n```\n\n`estimates_customer_approvals` is the only reCAPTCHA action in the entire\nconsumer app. Approve in a browser.\n\n## Invoices — `GET /api/invoices/consumer/v1/invoices/$HCP_TOKEN` (verified)\n\nAn invoice token is **32 hex characters**, not the estimate's 129. The document\nis flat — no `{object, data}` wrappers.\n\n| Path | What it is |\n| --- | --- |\n| `.invoice_number` | Human-facing invoice number |\n| `.status` | Display string, e.g. `paid` |\n| `.due_amount` | **integer cents** — the field that decides paid-ness |\n| `.subtotal` / `.total` / `.amount` | **integer cents** |\n| `.company_info.{name,phone_number,email,website,organization_uuid}` | The contractor |\n| `.customer.{uuid,email,mobile_number,card_on_file}` | You |\n| `.payment_options.can_pay_online` | Whether it can still be paid online |\n| `.invoice_count` | How many invoices the job carries |\n\nNo line items, and no tax field — tax is `total - subtotal`.\n\n```sh\njq -r '\"#\\(.invoice_number) [\\(.status)] due $\\(.due_amount/100) of $\\(.total/100)\"' invoice.json\n```\n\nThese 404 for an invoice token despite appearing in the bundles — do not use\nthem: `/api/invoices/consumer/invoices/{t}`, `/api/v2/consumer/invoices/{t}`,\n`/api/v2/consumer/sent_invoices/{t}`,\n`/api/invoices/linking/consumer/sources/{t}`,\n`/api/v2/consumer/invoices/{t}/invoice_or_estimate_pdf`.\n`/alpha/jobs/{t}` answers 401 — it wants a different credential.\n\n## Other endpoints (unverified)\n\nRead out of the SPA bundles; shapes unconfirmed.\n\n**Account-level portal (OTP login, spans all documents from one pro)** —\n`/api/customer_portal/request_otp`, `/api/customer_portal/verify_otp`,\n`/api/v2/customer_portal/magic_links`, `/api/v2/customer_portal/organizations`,\n`/api/v2/consumer/user/{log_in,log_out,profile,service_agreements}`\n\n**Jobs / media** — `/alpha/jobs/{id}`, `/api/customer_gallery/{id}`,\n`/api/attachments/customer_gallery/{id}`, `/alpha/after_actions/{id}`\n\n**Service agreements** — `/api/consumer/service_agreement/{id}`,\n`/api/v2/consumer/service_agreement/{id}`\n\n**Communications** — `/communications/consumer/organizations`,\n`/communications/consumer/preferences/{id}`,\n`/communications/consumer/consents/{opt_in,opt_out}`\n\n## Safety\n\n- The retrieval token is a bearer credential. Never commit it, never paste it\n  into a shared transcript, never put it in a URL you log.\n- Declining is not reversible from here.\n- Never attempt to script approval.\n\nFile v1.1.2:skill-card.md\n\n## Description:\n\nGuides customers through reading Housecall Pro estimates and invoices with shell commands, including checking amounts owed and declining estimate options without running an MCP server.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nCustomers and developers use this skill to inspect contractor-sent Housecall Pro estimates and invoices, summarize pricing and balances, and optionally decline an estimate option from a shell.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Document links grant access to customer estimates or invoices and can be used to decline estimate options.\n\nMitigation: Keep links private and out of shared transcripts or repositories; disclose them only to trusted recipients.\n\nRisk: Declining an option notifies the contractor and changes the estimate's state.\n\nMitigation: Review the estimate and selected option before sending a decline; re-read the estimate afterward to confirm its status.\n\nRisk: The skill includes unverified account, communications, media, and service-agreement endpoints beyond its stated purpose.\n\nMitigation: Do not probe those endpoints unless you explicitly intend that broader access.\n\n## Reference(s):\n\n- [ClawHub skill release](https://clawhub.ai/chrischall/skills/housecallpro-mcp)\n- [Housecall Pro customer portal recipes](references/recipes.md)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Guidance]\n\n**Output Format:** [Markdown with shell and jq examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Guidance covers estimates and invoices; approvals must be completed in a browser.]\n\n## Skill Version(s):\n\n1.1.2 (source: ClawHub release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.1.1: 4 files, 6508 bytes\n\nFiles: references/recipes.md (5775b), skill-card.md (1701b), SKILL.md (5867b), _meta.json (135b)\n\nFile v1.1.1:SKILL.md\n\n---\nname: housecallpro\ndescription: >-\n  Read a Housecall Pro estimate or invoice your contractor sent you — line\n  items, totals, tax, what is still owed, the company behind it — from a shell\n  with plain curl, instead of running the housecallpro-mcp server. Use when you want the data without the MCP, in a\n  script, or on a machine where the MCP isn't installed. Covers declining an\n  option, and why approving cannot be scripted.\n---\n\n# Housecall Pro customer portal via curl (no MCP)\n\nThis reads the **customer** side of Housecall Pro: the estimate or invoice a\ncontractor emails or texts you. It is not the Housecall Pro public API — that\none serves the business running on Housecall Pro and needs an API key from\ntheir account.\n\n**No browser bridge is needed.** `app.housecallpro.com` is not bot-walled; a\nplain `curl` gets a `200`. Do not reach for `fpx` here.\n\n## The one thing to know first\n\nThere is no account and no standing link: Housecall Pro sends a separate,\ndisposable link per document, so you work from whichever link you were sent.\n\nYour link is a **bearer credential**. Anyone holding it can read the estimate\nand decline it. Keep it in a variable or a file you control, never in a command\nyou paste into a shared transcript, and never commit it.\n\n## Setup\n\nTwo forms of link exist, and **estimates and invoices use different token\nshapes**: an estimate token is 129 characters (two 64-char hex halves joined by\n`_`), an invoice token is a bare 32-char hex string. The short link redirects to\nwhichever applies:\n\n```sh\n# What your contractor sent (short form)\nSHORT='https://pro.housecallpro.com/mobile_estimate/XXXXXXXXXX'   # or /mobile_invoice/…\n\n# Resolve it to the retrieval token (an estimate's is 129 chars, an invoice's 32)\nHCP_TOKEN=$(curl -sI \"$SHORT\" | tr -d '\\r' | awk 'tolower($1)==\"location:\"{print $2}' | sed 's#.*/##')\n\n# If you already have a client.housecallpro.com/estimates/… or /invoices/… link, just:\n# HCP_TOKEN='<the last path segment>'\n\necho \"${#HCP_TOKEN}\"   # 129 for an estimate, 32 for an invoice\n```\n\n## Read the estimate\n\n```sh\ncurl -s -H 'Accept: application/json' \\\n  \"https://app.housecallpro.com/alpha/customer_estimates/$HCP_TOKEN\" > estimate.json\n```\n\nNo `Authorization` header — reads carry the token in the path.\n\n**Money is integer cents.** `total_amount: 34639` means `$346.39`. Divide by 100\nbefore reporting anything. `tax.rate` is the exception: it is a fraction\n(`0.0825` = 8.25%) and must not be scaled.\n\nA one-line summary:\n\n```sh\njq -r '\n  \"\\(.company_name) — estimate #\\(.estimate.data.estimate_number)\",\n  \"For: \\(.customer_name) at \\(.estimate.data.address.data.printable_address)\",\n  (.options.data[] |\n    \"  \\(.name) [\\(.status)] $\\(.total_amount/100)\"),\n  (.options.data[].line_items.data[] |\n    \"    \\(.quantity) x \\(.name) @ $\\(.unit_price/100) = $\\(.amount/100)\")\n' estimate.json\n```\n\nMore recipes, including the company lookup and the full field map, are in\n[`references/recipes.md`](references/recipes.md).\n\n## Is it still awaiting me?\n\n```sh\njq -r '.options.data[] | select(.approval_date == null and .status != \"Declined\")\n       | \"OPEN: \\(.name) $\\(.total_amount/100)\"' estimate.json\n```\n\n## Declining an option\n\nDeclining works from a shell. It is the only mutation that does — and it tells\nthe contractor you are not proceeding, so read the estimate first.\n\n```sh\nOPTION_ID=$(jq -r '.options.data[0].id' estimate.json)   # est_…\n\ncurl -s -X POST \\\n  -H \"Authorization: Token $HCP_TOKEN\" \\\n  --data-urlencode \"estimate_option_uuids[]=$OPTION_ID\" \\\n  https://app.housecallpro.com/api/estimates/estimate_options/customer_declines\n```\n\nNote the asymmetry: **writes put the token in the `Authorization` header**,\nreads put it in the path.\n\n**A 2xx is not proof.** Re-read and check the option actually moved:\n\n```sh\ncurl -s \"https://app.housecallpro.com/alpha/customer_estimates/$HCP_TOKEN\" \\\n  | jq -r '.options.data[] | \"\\(.id) \\(.status) approval_date=\\(.approval_date)\"'\n```\n\n## Approving cannot be scripted\n\n`POST /api/estimates/estimate_options/customer_approvals` requires a\n`response_token` — a **reCAPTCHA v3 token** minted in-page for the action\n`estimates_customer_approvals`. No shell client can produce one, and neither can\nthe fetchproxy bridge (it issues `fetch` calls; it does not run page JS).\n\nOpen the link in a browser and press Approve there. Do not try to work around\nthis: approving is a binding commitment to a quoted price.\n\n## Failure modes\n\n| Symptom | Meaning |\n| --- | --- |\n| `401` / `403` | The link expired or was revoked. Ask your contractor to resend it. |\n| `404` (HTML) | The document was deleted or re-issued; the old link is dead. |\n| `$HCP_TOKEN` is empty or not 129 chars | The short link expired before it could redirect. |\n| A `200` that isn't JSON | Same as above — the link is no longer valid. |\n\n## Invoices\n\n```sh\ncurl -s -H 'Accept: application/json' \\\n  \"https://app.housecallpro.com/api/invoices/consumer/v1/invoices/$HCP_TOKEN\" > invoice.json\n```\n\n**The `v1` segment is required.** `/api/invoices/consumer/invoices/<token>` —\nthe same path without it — returns 404, as do the `/api/v2/consumer/invoices/…`\nand `/api/v2/consumer/sent_invoices/…` paths that also appear in the app's\nJavaScript. Only this one answers.\n\n```sh\njq -r '\"\\(.company_info.name) — invoice #\\(.invoice_number) [\\(.status)]\",\n       \"  subtotal $\\(.subtotal/100)\",\n       \"  tax      $\\((.total - .subtotal)/100)\",\n       \"  total    $\\(.total/100)\",\n       \"  due      $\\(.due_amount/100)\"' invoice.json\n```\n\nTwo things to know: the invoice document has **no line items** (a paid invoice\nrenders as a summary in the portal, and the API returns exactly that), and **no\ntax field** — the tax figure is `total - subtotal`. Money is integer cents here\ntoo. Use `due_amount` rather than `status` to decide whether it is settled.\n\nFile v1.1.1:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"housecallpro-mcp\",\n  \"version\": \"1.1.1\",\n  \"publishedAt\": 1790991631459\n}\n\nFile v1.1.1:references/recipes.md\n\n# Housecall Pro consumer API — recipes\n\nEvery recipe assumes `HCP_TOKEN` holds the 129-character retrieval token, as set\nup in `SKILL.md`. Endpoints marked **unverified** were read out of the SPA's\nJavaScript but never exercised against a real document.\n\n## Field map — `GET /alpha/customer_estimates/$HCP_TOKEN` (verified)\n\nThe envelope nests under `{object, data}` wrappers.\n\n| Path | What it is |\n| --- | --- |\n| `.estimate.data.estimate_number` | Human-facing estimate number |\n| `.estimate.data.estimate_uuid` | `csr_…`, the estimate's id |\n| `.estimate.data.organization_id` | Company UUID — feeds the company lookup |\n| `.estimate.data.customer_approval_mode` | e.g. `single_option` |\n| `.estimate.data.address.data.printable_address` | Service address |\n| `.options.data[]` | One entry per priced option |\n| `.options.data[].id` | `est_…` — **this is what you decline** |\n| `.options.data[].status` | e.g. `Awaiting Approval`, `Declined` |\n| `.options.data[].approval_date` | `null` until approved |\n| `.options.data[].sub_total` / `.total_amount` | **integer cents** |\n| `.options.data[].tax.data.rate` | Fraction (`0.0825`), NOT cents |\n| `.options.data[].tax.data.amount` | **integer cents** |\n| `.options.data[].line_items.data[]` | `name`, `description`, `quantity`, `kind`, `unit_price`, `amount` |\n| `.customer_name`, `.customer_email` | Who it is for |\n| `.message_from_pro` | Free-text note |\n| `.company_name`, `.company_phone_number`, `.company_email`, `.company_website` | The contractor |\n| `.payment_options.can_pay_online` | Whether online payment is offered |\n| `.deposit_requirement` | e.g. `not required` |\n| `.signatures_enabled` | Whether approval demands a signature |\n\n`kind` on a line item is `labor` or `material` — not `service`.\n\n## Totals, correctly scaled\n\n```sh\njq -r '.options.data[]\n  | \"\\(.name): subtotal $\\(.sub_total/100) + tax $\\(.tax.data.amount/100)\"\n  + \" (\\(.tax.data.rate*100)%) = $\\(.total_amount/100)\"' estimate.json\n```\n\n## Line items as TSV\n\n```sh\njq -r '.options.data[].line_items.data[]\n  | [.name, .kind, .quantity, (.unit_price/100), (.amount/100)] | @tsv' estimate.json\n```\n\n## Just the open options and their ids\n\n```sh\njq -r '.options.data[]\n  | select(.approval_date == null and .status != \"Declined\")\n  | \"\\(.id)\\t\\(.name)\\t$\\(.total_amount/100)\"' estimate.json\n```\n\n## The contractor behind it (verified)\n\n```sh\nORG=$(jq -r '.estimate.data.organization_id' estimate.json)\ncurl -s \"https://app.housecallpro.com/alpha/organizations/$ORG\" | jq\n```\n\nReturns `id`, `company_name`, `phone_number`, `email`, `website`, `logo_url`,\n`address`, `default_arrival_window`, `terms_url`, `founding_pro_uuid`. Needs no\nauth header at all.\n\n## Decline (verified shape, from the app's own code)\n\n```sh\ncurl -s -X POST \\\n  -H \"Authorization: Token $HCP_TOKEN\" \\\n  --data-urlencode \"estimate_option_uuids[]=est_XXXX\" \\\n  https://app.housecallpro.com/api/estimates/estimate_options/customer_declines\n```\n\nRepeat `--data-urlencode \"estimate_option_uuids[]=…\"` per option.\n\n## Approve — blocked\n\n```\nPOST /api/estimates/estimate_options/customer_approvals\n  Authorization: Token <token>\n  estimate_option_uuids[]=est_…\n  response_token=<reCAPTCHA v3 token>       <-- cannot be produced outside the page\n  signature[signature], signature[signatory_name], signature[signatory_user_agent]\n```\n\n`estimates_customer_approvals` is the only reCAPTCHA action in the entire\nconsumer app. Approve in a browser.\n\n## Invoices — `GET /api/invoices/consumer/v1/invoices/$HCP_TOKEN` (verified)\n\nAn invoice token is **32 hex characters**, not the estimate's 129. The document\nis flat — no `{object, data}` wrappers.\n\n| Path | What it is |\n| --- | --- |\n| `.invoice_number` | Human-facing invoice number |\n| `.status` | Display string, e.g. `paid` |\n| `.due_amount` | **integer cents** — the field that decides paid-ness |\n| `.subtotal` / `.total` / `.amount` | **integer cents** |\n| `.company_info.{name,phone_number,email,website,organization_uuid}` | The contractor |\n| `.customer.{uuid,email,mobile_number,card_on_file}` | You |\n| `.payment_options.can_pay_online` | Whether it can still be paid online |\n| `.invoice_count` | How many invoices the job carries |\n\nNo line items, and no tax field — tax is `total - subtotal`.\n\n```sh\njq -r '\"#\\(.invoice_number) [\\(.status)] due $\\(.due_amount/100) of $\\(.total/100)\"' invoice.json\n```\n\nThese 404 for an invoice token despite appearing in the bundles — do not use\nthem: `/api/invoices/consumer/invoices/{t}`, `/api/v2/consumer/invoices/{t}`,\n`/api/v2/consumer/sent_invoices/{t}`,\n`/api/invoices/linking/consumer/sources/{t}`,\n`/api/v2/consumer/invoices/{t}/invoice_or_estimate_pdf`.\n`/alpha/jobs/{t}` answers 401 — it wants a different credential.\n\n## Other endpoints (unverified)\n\nRead out of the SPA bundles; shapes unconfirmed.\n\n**Account-level portal (OTP login, spans all documents from one pro)** —\n`/api/customer_portal/request_otp`, `/api/customer_portal/verify_otp`,\n`/api/v2/customer_portal/magic_links`, `/api/v2/customer_portal/organizations`,\n`/api/v2/consumer/user/{log_in,log_out,profile,service_agreements}`\n\n**Jobs / media** — `/alpha/jobs/{id}`, `/api/customer_gallery/{id}`,\n`/api/attachments/customer_gallery/{id}`, `/alpha/after_actions/{id}`\n\n**Service agreements** — `/api/consumer/service_agreement/{id}`,\n`/api/v2/consumer/service_agreement/{id}`\n\n**Communications** — `/communications/consumer/organizations`,\n`/communications/consumer/preferences/{id}`,\n`/communications/consumer/consents/{opt_in,opt_out}`\n\n## Safety\n\n- The retrieval token is a bearer credential. Never commit it, never paste it\n  into a shared transcript, never put it in a URL you log.\n- Declining is not reversible from here.\n- Never attempt to script approval.\n\nFile v1.1.1:skill-card.md\n\n## Description:\n\nHelps agents read customer-facing Housecall Pro estimates and invoices with shell commands, and explains how to decline an estimate option safely.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nCustomers and their agents use this skill to inspect contractor-sent estimates and invoices, summarize amounts owed, and optionally decline an estimate option without installing the MCP server.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Anyone with a document link can read it or decline an estimate option.\n\nMitigation: Treat links and tokens as bearer credentials; keep them out of shared logs, transcripts, and commits.\n\nRisk: Declining an option notifies the contractor that the customer is not proceeding.\n\nMitigation: Read the estimate and confirm the intended option before sending a decline request.\n\n## Reference(s):\n\n- [Housecall Pro consumer API recipes](references/recipes.md)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Guidance]\n\n**Output Format:** [Markdown with shell and jq examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Estimate and invoice summaries require a valid document link; approvals must be completed in a browser.]\n\n## Skill Version(s):\n\n1.1.1 (source: server-resolved ClawHub release)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.1.0: 4 files, 6817 bytes\n\nFiles: references/recipes.md (5775b), skill-card.md (2395b), SKILL.md (5867b), _meta.json (135b)\n\nFile v1.1.0:SKILL.md\n\n---\nname: housecallpro\ndescription: >-\n  Read a Housecall Pro estimate or invoice your contractor sent you — line\n  items, totals, tax, what is still owed, the company behind it — from a shell\n  with plain curl, instead of running the housecallpro-mcp server. Use when you want the data without the MCP, in a\n  script, or on a machine where the MCP isn't installed. Covers declining an\n  option, and why approving cannot be scripted.\n---\n\n# Housecall Pro customer portal via curl (no MCP)\n\nThis reads the **customer** side of Housecall Pro: the estimate or invoice a\ncontractor emails or texts you. It is not the Housecall Pro public API — that\none serves the business running on Housecall Pro and needs an API key from\ntheir account.\n\n**No browser bridge is needed.** `app.housecallpro.com` is not bot-walled; a\nplain `curl` gets a `200`. Do not reach for `fpx` here.\n\n## The one thing to know first\n\nThere is no account and no standing link: Housecall Pro sends a separate,\ndisposable link per document, so you work from whichever link you were sent.\n\nYour link is a **bearer credential**. Anyone holding it can read the estimate\nand decline it. Keep it in a variable or a file you control, never in a command\nyou paste into a shared transcript, and never commit it.\n\n## Setup\n\nTwo forms of link exist, and **estimates and invoices use different token\nshapes**: an estimate token is 129 characters (two 64-char hex halves joined by\n`_`), an invoice token is a bare 32-char hex string. The short link redirects to\nwhichever applies:\n\n```sh\n# What your contractor sent (short form)\nSHORT='https://pro.housecallpro.com/mobile_estimate/XXXXXXXXXX'   # or /mobile_invoice/…\n\n# Resolve it to the retrieval token (an estimate's is 129 chars, an invoice's 32)\nHCP_TOKEN=$(curl -sI \"$SHORT\" | tr -d '\\r' | awk 'tolower($1)==\"location:\"{print $2}' | sed 's#.*/##')\n\n# If you already have a client.housecallpro.com/estimates/… or /invoices/… link, just:\n# HCP_TOKEN='<the last path segment>'\n\necho \"${#HCP_TOKEN}\"   # 129 for an estimate, 32 for an invoice\n```\n\n## Read the estimate\n\n```sh\ncurl -s -H 'Accept: application/json' \\\n  \"https://app.housecallpro.com/alpha/customer_estimates/$HCP_TOKEN\" > estimate.json\n```\n\nNo `Authorization` header — reads carry the token in the path.\n\n**Money is integer cents.** `total_amount: 34639` means `$346.39`. Divide by 100\nbefore reporting anything. `tax.rate` is the exception: it is a fraction\n(`0.0825` = 8.25%) and must not be scaled.\n\nA one-line summary:\n\n```sh\njq -r '\n  \"\\(.company_name) — estimate #\\(.estimate.data.estimate_number)\",\n  \"For: \\(.customer_name) at \\(.estimate.data.address.data.printable_address)\",\n  (.options.data[] |\n    \"  \\(.name) [\\(.status)] $\\(.total_amount/100)\"),\n  (.options.data[].line_items.data[] |\n    \"    \\(.quantity) x \\(.name) @ $\\(.unit_price/100) = $\\(.amount/100)\")\n' estimate.json\n```\n\nMore recipes, including the company lookup and the full field map, are in\n[`references/recipes.md`](references/recipes.md).\n\n## Is it still awaiting me?\n\n```sh\njq -r '.options.data[] | select(.approval_date == null and .status != \"Declined\")\n       | \"OPEN: \\(.name) $\\(.total_amount/100)\"' estimate.json\n```\n\n## Declining an option\n\nDeclining works from a shell. It is the only mutation that does — and it tells\nthe contractor you are not proceeding, so read the estimate first.\n\n```sh\nOPTION_ID=$(jq -r '.options.data[0].id' estimate.json)   # est_…\n\ncurl -s -X POST \\\n  -H \"Authorization: Token $HCP_TOKEN\" \\\n  --data-urlencode \"estimate_option_uuids[]=$OPTION_ID\" \\\n  https://app.housecallpro.com/api/estimates/estimate_options/customer_declines\n```\n\nNote the asymmetry: **writes put the token in the `Authorization` header**,\nreads put it in the path.\n\n**A 2xx is not proof.** Re-read and check the option actually moved:\n\n```sh\ncurl -s \"https://app.housecallpro.com/alpha/customer_estimates/$HCP_TOKEN\" \\\n  | jq -r '.options.data[] | \"\\(.id) \\(.status) approval_date=\\(.approval_date)\"'\n```\n\n## Approving cannot be scripted\n\n`POST /api/estimates/estimate_options/customer_approvals` requires a\n`response_token` — a **reCAPTCHA v3 token** minted in-page for the action\n`estimates_customer_approvals`. No shell client can produce one, and neither can\nthe fetchproxy bridge (it issues `fetch` calls; it does not run page JS).\n\nOpen the link in a browser and press Approve there. Do not try to work around\nthis: approving is a binding commitment to a quoted price.\n\n## Failure modes\n\n| Symptom | Meaning |\n| --- | --- |\n| `401` / `403` | The link expired or was revoked. Ask your contractor to resend it. |\n| `404` (HTML) | The document was deleted or re-issued; the old link is dead. |\n| `$HCP_TOKEN` is empty or not 129 chars | The short link expired before it could redirect. |\n| A `200` that isn't JSON | Same as above — the link is no longer valid. |\n\n## Invoices\n\n```sh\ncurl -s -H 'Accept: application/json' \\\n  \"https://app.housecallpro.com/api/invoices/consumer/v1/invoices/$HCP_TOKEN\" > invoice.json\n```\n\n**The `v1` segment is required.** `/api/invoices/consumer/invoices/<token>` —\nthe same path without it — returns 404, as do the `/api/v2/consumer/invoices/…`\nand `/api/v2/consumer/sent_invoices/…` paths that also appear in the app's\nJavaScript. Only this one answers.\n\n```sh\njq -r '\"\\(.company_info.name) — invoice #\\(.invoice_number) [\\(.status)]\",\n       \"  subtotal $\\(.subtotal/100)\",\n       \"  tax      $\\((.total - .subtotal)/100)\",\n       \"  total    $\\(.total/100)\",\n       \"  due      $\\(.due_amount/100)\"' invoice.json\n```\n\nTwo things to know: the invoice document has **no line items** (a paid invoice\nrenders as a summary in the portal, and the API returns exactly that), and **no\ntax field** — the tax figure is `total - subtotal`. Money is integer cents here\ntoo. Use `due_amount` rather than `status` to decide whether it is settled.\n\nFile v1.1.0:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"housecallpro-mcp\",\n  \"version\": \"1.1.0\",\n  \"publishedAt\": 1790262705174\n}\n\nFile v1.1.0:references/recipes.md\n\n# Housecall Pro consumer API — recipes\n\nEvery recipe assumes `HCP_TOKEN` holds the 129-character retrieval token, as set\nup in `SKILL.md`. Endpoints marked **unverified** were read out of the SPA's\nJavaScript but never exercised against a real document.\n\n## Field map — `GET /alpha/customer_estimates/$HCP_TOKEN` (verified)\n\nThe envelope nests under `{object, data}` wrappers.\n\n| Path | What it is |\n| --- | --- |\n| `.estimate.data.estimate_number` | Human-facing estimate number |\n| `.estimate.data.estimate_uuid` | `csr_…`, the estimate's id |\n| `.estimate.data.organization_id` | Company UUID — feeds the company lookup |\n| `.estimate.data.customer_approval_mode` | e.g. `single_option` |\n| `.estimate.data.address.data.printable_address` | Service address |\n| `.options.data[]` | One entry per priced option |\n| `.options.data[].id` | `est_…` — **this is what you decline** |\n| `.options.data[].status` | e.g. `Awaiting Approval`, `Declined` |\n| `.options.data[].approval_date` | `null` until approved |\n| `.options.data[].sub_total` / `.total_amount` | **integer cents** |\n| `.options.data[].tax.data.rate` | Fraction (`0.0825`), NOT cents |\n| `.options.data[].tax.data.amount` | **integer cents** |\n| `.options.data[].line_items.data[]` | `name`, `description`, `quantity`, `kind`, `unit_price`, `amount` |\n| `.customer_name`, `.customer_email` | Who it is for |\n| `.message_from_pro` | Free-text note |\n| `.company_name`, `.company_phone_number`, `.company_email`, `.company_website` | The contractor |\n| `.payment_options.can_pay_online` | Whether online payment is offered |\n| `.deposit_requirement` | e.g. `not required` |\n| `.signatures_enabled` | Whether approval demands a signature |\n\n`kind` on a line item is `labor` or `material` — not `service`.\n\n## Totals, correctly scaled\n\n```sh\njq -r '.options.data[]\n  | \"\\(.name): subtotal $\\(.sub_total/100) + tax $\\(.tax.data.amount/100)\"\n  + \" (\\(.tax.data.rate*100)%) = $\\(.total_amount/100)\"' estimate.json\n```\n\n## Line items as TSV\n\n```sh\njq -r '.options.data[].line_items.data[]\n  | [.name, .kind, .quantity, (.unit_price/100), (.amount/100)] | @tsv' estimate.json\n```\n\n## Just the open options and their ids\n\n```sh\njq -r '.options.data[]\n  | select(.approval_date == null and .status != \"Declined\")\n  | \"\\(.id)\\t\\(.name)\\t$\\(.total_amount/100)\"' estimate.json\n```\n\n## The contractor behind it (verified)\n\n```sh\nORG=$(jq -r '.estimate.data.organization_id' estimate.json)\ncurl -s \"https://app.housecallpro.com/alpha/organizations/$ORG\" | jq\n```\n\nReturns `id`, `company_name`, `phone_number`, `email`, `website`, `logo_url`,\n`address`, `default_arrival_window`, `terms_url`, `founding_pro_uuid`. Needs no\nauth header at all.\n\n## Decline (verified shape, from the app's own code)\n\n```sh\ncurl -s -X POST \\\n  -H \"Authorization: Token $HCP_TOKEN\" \\\n  --data-urlencode \"estimate_option_uuids[]=est_XXXX\" \\\n  https://app.housecallpro.com/api/estimates/estimate_options/customer_declines\n```\n\nRepeat `--data-urlencode \"estimate_option_uuids[]=…\"` per option.\n\n## Approve — blocked\n\n```\nPOST /api/estimates/estimate_options/customer_approvals\n  Authorization: Token <token>\n  estimate_option_uuids[]=est_…\n  response_token=<reCAPTCHA v3 token>       <-- cannot be produced outside the page\n  signature[signature], signature[signatory_name], signature[signatory_user_agent]\n```\n\n`estimates_customer_approvals` is the only reCAPTCHA action in the entire\nconsumer app. Approve in a browser.\n\n## Invoices — `GET /api/invoices/consumer/v1/invoices/$HCP_TOKEN` (verified)\n\nAn invoice token is **32 hex characters**, not the estimate's 129. The document\nis flat — no `{object, data}` wrappers.\n\n| Path | What it is |\n| --- | --- |\n| `.invoice_number` | Human-facing invoice number |\n| `.status` | Display string, e.g. `paid` |\n| `.due_amount` | **integer cents** — the field that decides paid-ness |\n| `.subtotal` / `.total` / `.amount` | **integer cents** |\n| `.company_info.{name,phone_number,email,website,organization_uuid}` | The contractor |\n| `.customer.{uuid,email,mobile_number,card_on_file}` | You |\n| `.payment_options.can_pay_online` | Whether it can still be paid online |\n| `.invoice_count` | How many invoices the job carries |\n\nNo line items, and no tax field — tax is `total - subtotal`.\n\n```sh\njq -r '\"#\\(.invoice_number) [\\(.status)] due $\\(.due_amount/100) of $\\(.total/100)\"' invoice.json\n```\n\nThese 404 for an invoice token despite appearing in the bundles — do not use\nthem: `/api/invoices/consumer/invoices/{t}`, `/api/v2/consumer/invoices/{t}`,\n`/api/v2/consumer/sent_invoices/{t}`,\n`/api/invoices/linking/consumer/sources/{t}`,\n`/api/v2/consumer/invoices/{t}/invoice_or_estimate_pdf`.\n`/alpha/jobs/{t}` answers 401 — it wants a different credential.\n\n## Other endpoints (unverified)\n\nRead out of the SPA bundles; shapes unconfirmed.\n\n**Account-level portal (OTP login, spans all documents from one pro)** —\n`/api/customer_portal/request_otp`, `/api/customer_portal/verify_otp`,\n`/api/v2/customer_portal/magic_links`, `/api/v2/customer_portal/organizations`,\n`/api/v2/consumer/user/{log_in,log_out,profile,service_agreements}`\n\n**Jobs / media** — `/alpha/jobs/{id}`, `/api/customer_gallery/{id}`,\n`/api/attachments/customer_gallery/{id}`, `/alpha/after_actions/{id}`\n\n**Service agreements** — `/api/consumer/service_agreement/{id}`,\n`/api/v2/consumer/service_agreement/{id}`\n\n**Communications** — `/communications/consumer/organizations`,\n`/communications/consumer/preferences/{id}`,\n`/communications/consumer/consents/{opt_in,opt_out}`\n\n## Safety\n\n- The retrieval token is a bearer credential. Never commit it, never paste it\n  into a shared transcript, never put it in a URL you log.\n- Declining is not reversible from here.\n- Never attempt to script approval.\n\nFile v1.1.0:skill-card.md\n\n## Description:\n\nReads Housecall Pro customer estimates and invoices from disposable customer portal links using curl, including totals, open options, company lookup, invoice status, and documented decline behavior.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nExternal users and developers use this skill to retrieve and interpret Housecall Pro customer estimate or invoice data from a supplied document link using shell commands. It helps agents summarize amounts, line items, tax, due balances, contractor details, and the limited decline workflow without running the MCP server.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Housecall Pro document links are bearer credentials that may expose estimate or invoice data to anyone with the token.\n\nMitigation: Keep tokens out of shared transcripts, logs, commits, and pasted commands; store them only in controlled variables or local files.\n\nRisk: The documented decline command can notify the contractor that the customer is not proceeding.\n\nMitigation: Read and verify the estimate first, run decline commands only when intended, and re-read the document afterward to confirm the resulting status.\n\nRisk: Attempting to script approval could bypass the intended browser confirmation flow for a binding commitment.\n\nMitigation: Do not script approval; use the browser approval flow when the customer intends to approve an estimate.\n\n## Reference(s):\n\n- [Housecall Pro consumer API recipes](artifact/references/recipes.md)\n- [ClawHub skill page](https://clawhub.ai/chrischall/skills/housecallpro-mcp)\n\n## Skill Output:\n\n**Output Type(s):** [Guidance, Markdown, Shell commands, Code]\n\n**Output Format:** [Markdown with shell and jq code blocks]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [May produce curl commands and jq filters that read JSON estimate or invoice responses; decline commands perform a state-changing action.]\n\n## Skill Version(s):\n\n1.1.0 (source: server release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.0.3: 4 files, 6831 bytes\n\nFiles: references/recipes.md (5775b), skill-card.md (2443b), SKILL.md (5867b), _meta.json (135b)\n\nFile v1.0.3:SKILL.md\n\n---\nname: housecallpro\ndescription: >-\n  Read a Housecall Pro estimate or invoice your contractor sent you — line\n  items, totals, tax, what is still owed, the company behind it — from a shell\n  with plain curl, instead of running the housecallpro-mcp server. Use when you want the data without the MCP, in a\n  script, or on a machine where the MCP isn't installed. Covers declining an\n  option, and why approving cannot be scripted.\n---\n\n# Housecall Pro customer portal via curl (no MCP)\n\nThis reads the **customer** side of Housecall Pro: the estimate or invoice a\ncontractor emails or texts you. It is not the Housecall Pro public API — that\none serves the business running on Housecall Pro and needs an API key from\ntheir account.\n\n**No browser bridge is needed.** `app.housecallpro.com` is not bot-walled; a\nplain `curl` gets a `200`. Do not reach for `fpx` here.\n\n## The one thing to know first\n\nThere is no account and no standing link: Housecall Pro sends a separate,\ndisposable link per document, so you work from whichever link you were sent.\n\nYour link is a **bearer credential**. Anyone holding it can read the estimate\nand decline it. Keep it in a variable or a file you control, never in a command\nyou paste into a shared transcript, and never commit it.\n\n## Setup\n\nTwo forms of link exist, and **estimates and invoices use different token\nshapes**: an estimate token is 129 characters (two 64-char hex halves joined by\n`_`), an invoice token is a bare 32-char hex string. The short link redirects to\nwhichever applies:\n\n```sh\n# What your contractor sent (short form)\nSHORT='https://pro.housecallpro.com/mobile_estimate/XXXXXXXXXX'   # or /mobile_invoice/…\n\n# Resolve it to the retrieval token (an estimate's is 129 chars, an invoice's 32)\nHCP_TOKEN=$(curl -sI \"$SHORT\" | tr -d '\\r' | awk 'tolower($1)==\"location:\"{print $2}' | sed 's#.*/##')\n\n# If you already have a client.housecallpro.com/estimates/… or /invoices/… link, just:\n# HCP_TOKEN='<the last path segment>'\n\necho \"${#HCP_TOKEN}\"   # 129 for an estimate, 32 for an invoice\n```\n\n## Read the estimate\n\n```sh\ncurl -s -H 'Accept: application/json' \\\n  \"https://app.housecallpro.com/alpha/customer_estimates/$HCP_TOKEN\" > estimate.json\n```\n\nNo `Authorization` header — reads carry the token in the path.\n\n**Money is integer cents.** `total_amount: 34639` means `$346.39`. Divide by 100\nbefore reporting anything. `tax.rate` is the exception: it is a fraction\n(`0.0825` = 8.25%) and must not be scaled.\n\nA one-line summary:\n\n```sh\njq -r '\n  \"\\(.company_name) — estimate #\\(.estimate.data.estimate_number)\",\n  \"For: \\(.customer_name) at \\(.estimate.data.address.data.printable_address)\",\n  (.options.data[] |\n    \"  \\(.name) [\\(.status)] $\\(.total_amount/100)\"),\n  (.options.data[].line_items.data[] |\n    \"    \\(.quantity) x \\(.name) @ $\\(.unit_price/100) = $\\(.amount/100)\")\n' estimate.json\n```\n\nMore recipes, including the company lookup and the full field map, are in\n[`references/recipes.md`](references/recipes.md).\n\n## Is it still awaiting me?\n\n```sh\njq -r '.options.data[] | select(.approval_date == null and .status != \"Declined\")\n       | \"OPEN: \\(.name) $\\(.total_amount/100)\"' estimate.json\n```\n\n## Declining an option\n\nDeclining works from a shell. It is the only mutation that does — and it tells\nthe contractor you are not proceeding, so read the estimate first.\n\n```sh\nOPTION_ID=$(jq -r '.options.data[0].id' estimate.json)   # est_…\n\ncurl -s -X POST \\\n  -H \"Authorization: Token $HCP_TOKEN\" \\\n  --data-urlencode \"estimate_option_uuids[]=$OPTION_ID\" \\\n  https://app.housecallpro.com/api/estimates/estimate_options/customer_declines\n```\n\nNote the asymmetry: **writes put the token in the `Authorization` header**,\nreads put it in the path.\n\n**A 2xx is not proof.** Re-read and check the option actually moved:\n\n```sh\ncurl -s \"https://app.housecallpro.com/alpha/customer_estimates/$HCP_TOKEN\" \\\n  | jq -r '.options.data[] | \"\\(.id) \\(.status) approval_date=\\(.approval_date)\"'\n```\n\n## Approving cannot be scripted\n\n`POST /api/estimates/estimate_options/customer_approvals` requires a\n`response_token` — a **reCAPTCHA v3 token** minted in-page for the action\n`estimates_customer_approvals`. No shell client can produce one, and neither can\nthe fetchproxy bridge (it issues `fetch` calls; it does not run page JS).\n\nOpen the link in a browser and press Approve there. Do not try to work around\nthis: approving is a binding commitment to a quoted price.\n\n## Failure modes\n\n| Symptom | Meaning |\n| --- | --- |\n| `401` / `403` | The link expired or was revoked. Ask your contractor to resend it. |\n| `404` (HTML) | The document was deleted or re-issued; the old link is dead. |\n| `$HCP_TOKEN` is empty or not 129 chars | The short link expired before it could redirect. |\n| A `200` that isn't JSON | Same as above — the link is no longer valid. |\n\n## Invoices\n\n```sh\ncurl -s -H 'Accept: application/json' \\\n  \"https://app.housecallpro.com/api/invoices/consumer/v1/invoices/$HCP_TOKEN\" > invoice.json\n```\n\n**The `v1` segment is required.** `/api/invoices/consumer/invoices/<token>` —\nthe same path without it — returns 404, as do the `/api/v2/consumer/invoices/…`\nand `/api/v2/consumer/sent_invoices/…` paths that also appear in the app's\nJavaScript. Only this one answers.\n\n```sh\njq -r '\"\\(.company_info.name) — invoice #\\(.invoice_number) [\\(.status)]\",\n       \"  subtotal $\\(.subtotal/100)\",\n       \"  tax      $\\((.total - .subtotal)/100)\",\n       \"  total    $\\(.total/100)\",\n       \"  due      $\\(.due_amount/100)\"' invoice.json\n```\n\nTwo things to know: the invoice document has **no line items** (a paid invoice\nrenders as a summary in the portal, and the API returns exactly that), and **no\ntax field** — the tax figure is `total - subtotal`. Money is integer cents here\ntoo. Use `due_amount` rather than `status` to decide whether it is settled.\n\nFile v1.0.3:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"housecallpro-mcp\",\n  \"version\": \"1.0.3\",\n  \"publishedAt\": 1790199573791\n}\n\nFile v1.0.3:references/recipes.md\n\n# Housecall Pro consumer API — recipes\n\nEvery recipe assumes `HCP_TOKEN` holds the 129-character retrieval token, as set\nup in `SKILL.md`. Endpoints marked **unverified** were read out of the SPA's\nJavaScript but never exercised against a real document.\n\n## Field map — `GET /alpha/customer_estimates/$HCP_TOKEN` (verified)\n\nThe envelope nests under `{object, data}` wrappers.\n\n| Path | What it is |\n| --- | --- |\n| `.estimate.data.estimate_number` | Human-facing estimate number |\n| `.estimate.data.estimate_uuid` | `csr_…`, the estimate's id |\n| `.estimate.data.organization_id` | Company UUID — feeds the company lookup |\n| `.estimate.data.customer_approval_mode` | e.g. `single_option` |\n| `.estimate.data.address.data.printable_address` | Service address |\n| `.options.data[]` | One entry per priced option |\n| `.options.data[].id` | `est_…` — **this is what you decline** |\n| `.options.data[].status` | e.g. `Awaiting Approval`, `Declined` |\n| `.options.data[].approval_date` | `null` until approved |\n| `.options.data[].sub_total` / `.total_amount` | **integer cents** |\n| `.options.data[].tax.data.rate` | Fraction (`0.0825`), NOT cents |\n| `.options.data[].tax.data.amount` | **integer cents** |\n| `.options.data[].line_items.data[]` | `name`, `description`, `quantity`, `kind`, `unit_price`, `amount` |\n| `.customer_name`, `.customer_email` | Who it is for |\n| `.message_from_pro` | Free-text note |\n| `.company_name`, `.company_phone_number`, `.company_email`, `.company_website` | The contractor |\n| `.payment_options.can_pay_online` | Whether online payment is offered |\n| `.deposit_requirement` | e.g. `not required` |\n| `.signatures_enabled` | Whether approval demands a signature |\n\n`kind` on a line item is `labor` or `material` — not `service`.\n\n## Totals, correctly scaled\n\n```sh\njq -r '.options.data[]\n  | \"\\(.name): subtotal $\\(.sub_total/100) + tax $\\(.tax.data.amount/100)\"\n  + \" (\\(.tax.data.rate*100)%) = $\\(.total_amount/100)\"' estimate.json\n```\n\n## Line items as TSV\n\n```sh\njq -r '.options.data[].line_items.data[]\n  | [.name, .kind, .quantity, (.unit_price/100), (.amount/100)] | @tsv' estimate.json\n```\n\n## Just the open options and their ids\n\n```sh\njq -r '.options.data[]\n  | select(.approval_date == null and .status != \"Declined\")\n  | \"\\(.id)\\t\\(.name)\\t$\\(.total_amount/100)\"' estimate.json\n```\n\n## The contractor behind it (verified)\n\n```sh\nORG=$(jq -r '.estimate.data.organization_id' estimate.json)\ncurl -s \"https://app.housecallpro.com/alpha/organizations/$ORG\" | jq\n```\n\nReturns `id`, `company_name`, `phone_number`, `email`, `website`, `logo_url`,\n`address`, `default_arrival_window`, `terms_url`, `founding_pro_uuid`. Needs no\nauth header at all.\n\n## Decline (verified shape, from the app's own code)\n\n```sh\ncurl -s -X POST \\\n  -H \"Authorization: Token $HCP_TOKEN\" \\\n  --data-urlencode \"estimate_option_uuids[]=est_XXXX\" \\\n  https://app.housecallpro.com/api/estimates/estimate_options/customer_declines\n```\n\nRepeat `--data-urlencode \"estimate_option_uuids[]=…\"` per option.\n\n## Approve — blocked\n\n```\nPOST /api/estimates/estimate_options/customer_approvals\n  Authorization: Token <token>\n  estimate_option_uuids[]=est_…\n  response_token=<reCAPTCHA v3 token>       <-- cannot be produced outside the page\n  signature[signature], signature[signatory_name], signature[signatory_user_agent]\n```\n\n`estimates_customer_approvals` is the only reCAPTCHA action in the entire\nconsumer app. Approve in a browser.\n\n## Invoices — `GET /api/invoices/consumer/v1/invoices/$HCP_TOKEN` (verified)\n\nAn invoice token is **32 hex characters**, not the estimate's 129. The document\nis flat — no `{object, data}` wrappers.\n\n| Path | What it is |\n| --- | --- |\n| `.invoice_number` | Human-facing invoice number |\n| `.status` | Display string, e.g. `paid` |\n| `.due_amount` | **integer cents** — the field that decides paid-ness |\n| `.subtotal` / `.total` / `.amount` | **integer cents** |\n| `.company_info.{name,phone_number,email,website,organization_uuid}` | The contractor |\n| `.customer.{uuid,email,mobile_number,card_on_file}` | You |\n| `.payment_options.can_pay_online` | Whether it can still be paid online |\n| `.invoice_count` | How many invoices the job carries |\n\nNo line items, and no tax field — tax is `total - subtotal`.\n\n```sh\njq -r '\"#\\(.invoice_number) [\\(.status)] due $\\(.due_amount/100) of $\\(.total/100)\"' invoice.json\n```\n\nThese 404 for an invoice token despite appearing in the bundles — do not use\nthem: `/api/invoices/consumer/invoices/{t}`, `/api/v2/consumer/invoices/{t}`,\n`/api/v2/consumer/sent_invoices/{t}`,\n`/api/invoices/linking/consumer/sources/{t}`,\n`/api/v2/consumer/invoices/{t}/invoice_or_estimate_pdf`.\n`/alpha/jobs/{t}` answers 401 — it wants a different credential.\n\n## Other endpoints (unverified)\n\nRead out of the SPA bundles; shapes unconfirmed.\n\n**Account-level portal (OTP login, spans all documents from one pro)** —\n`/api/customer_portal/request_otp`, `/api/customer_portal/verify_otp`,\n`/api/v2/customer_portal/magic_links`, `/api/v2/customer_portal/organizations`,\n`/api/v2/consumer/user/{log_in,log_out,profile,service_agreements}`\n\n**Jobs / media** — `/alpha/jobs/{id}`, `/api/customer_gallery/{id}`,\n`/api/attachments/customer_gallery/{id}`, `/alpha/after_actions/{id}`\n\n**Service agreements** — `/api/consumer/service_agreement/{id}`,\n`/api/v2/consumer/service_agreement/{id}`\n\n**Communications** — `/communications/consumer/organizations`,\n`/communications/consumer/preferences/{id}`,\n`/communications/consumer/consents/{opt_in,opt_out}`\n\n## Safety\n\n- The retrieval token is a bearer credential. Never commit it, never paste it\n  into a shared transcript, never put it in a URL you log.\n- Declining is not reversible from here.\n- Never attempt to script approval.\n\nFile v1.0.3:skill-card.md\n\n## Description:\n\nRead Housecall Pro customer estimates and invoices from document links with plain curl, summarize amounts and line items, and explain why approval must be completed in a browser.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nExternal Housecall Pro customers and developers use this skill to retrieve estimate or invoice details from document links, summarize totals and line items, and understand the limited shell-supported actions.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Housecall Pro document links and tokens act as bearer credentials.\n\nMitigation: Keep links and tokens out of shared transcripts, logs, and commits; store them only in local variables or files you control.\n\nRisk: The skill includes a real decline action that can notify the contractor that the customer is not proceeding.\n\nMitigation: Read the estimate first, confirm the selected option id, and re-read the document after any decline request to verify the result.\n\nRisk: The artifact lists unverified portal endpoints beyond the documented estimate and invoice flows.\n\nMitigation: Use the verified read and decline examples for normal workflows, and avoid unverified endpoints unless deliberately investigating broader portal behavior.\n\nRisk: Approval cannot be safely scripted because the portal requires an in-page reCAPTCHA token.\n\nMitigation: Complete approval in the browser and do not attempt to bypass the portal's approval flow.\n\n## Reference(s):\n\n- [Housecall Pro consumer API recipes](references/recipes.md)\n- [ClawHub skill page](https://clawhub.ai/chrischall/skills/housecallpro-mcp)\n\n## Skill Output:\n\n**Output Type(s):** [text, markdown, shell commands, configuration, guidance]\n\n**Output Format:** [Markdown guidance with shell command examples and JSON/jq processing snippets]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Includes warnings for bearer-token handling, decline behavior, and browser-only approval.]\n\n## Skill Version(s):\n\n1.0.3 (source: server release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.0.2: 4 files, 6894 bytes\n\nFiles: references/recipes.md (5775b), skill-card.md (2566b), SKILL.md (5867b), _meta.json (135b)\n\nFile v1.0.2:SKILL.md\n\n---\nname: housecallpro\ndescription: >-\n  Read a Housecall Pro estimate or invoice your contractor sent you — line\n  items, totals, tax, what is still owed, the company behind it — from a shell\n  with plain curl, instead of running the housecallpro-mcp server. Use when you want the data without the MCP, in a\n  script, or on a machine where the MCP isn't installed. Covers declining an\n  option, and why approving cannot be scripted.\n---\n\n# Housecall Pro customer portal via curl (no MCP)\n\nThis reads the **customer** side of Housecall Pro: the estimate or invoice a\ncontractor emails or texts you. It is not the Housecall Pro public API — that\none serves the business running on Housecall Pro and needs an API key from\ntheir account.\n\n**No browser bridge is needed.** `app.housecallpro.com` is not bot-walled; a\nplain `curl` gets a `200`. Do not reach for `fpx` here.\n\n## The one thing to know first\n\nThere is no account and no standing link: Housecall Pro sends a separate,\ndisposable link per document, so you work from whichever link you were sent.\n\nYour link is a **bearer credential**. Anyone holding it can read the estimate\nand decline it. Keep it in a variable or a file you control, never in a command\nyou paste into a shared transcript, and never commit it.\n\n## Setup\n\nTwo forms of link exist, and **estimates and invoices use different token\nshapes**: an estimate token is 129 characters (two 64-char hex halves joined by\n`_`), an invoice token is a bare 32-char hex string. The short link redirects to\nwhichever applies:\n\n```sh\n# What your contractor sent (short form)\nSHORT='https://pro.housecallpro.com/mobile_estimate/XXXXXXXXXX'   # or /mobile_invoice/…\n\n# Resolve it to the retrieval token (an estimate's is 129 chars, an invoice's 32)\nHCP_TOKEN=$(curl -sI \"$SHORT\" | tr -d '\\r' | awk 'tolower($1)==\"location:\"{print $2}' | sed 's#.*/##')\n\n# If you already have a client.housecallpro.com/estimates/… or /invoices/… link, just:\n# HCP_TOKEN='<the last path segment>'\n\necho \"${#HCP_TOKEN}\"   # 129 for an estimate, 32 for an invoice\n```\n\n## Read the estimate\n\n```sh\ncurl -s -H 'Accept: application/json' \\\n  \"https://app.housecallpro.com/alpha/customer_estimates/$HCP_TOKEN\" > estimate.json\n```\n\nNo `Authorization` header — reads carry the token in the path.\n\n**Money is integer cents.** `total_amount: 34639` means `$346.39`. Divide by 100\nbefore reporting anything. `tax.rate` is the exception: it is a fraction\n(`0.0825` = 8.25%) and must not be scaled.\n\nA one-line summary:\n\n```sh\njq -r '\n  \"\\(.company_name) — estimate #\\(.estimate.data.estimate_number)\",\n  \"For: \\(.customer_name) at \\(.estimate.data.address.data.printable_address)\",\n  (.options.data[] |\n    \"  \\(.name) [\\(.status)] $\\(.total_amount/100)\"),\n  (.options.data[].line_items.data[] |\n    \"    \\(.quantity) x \\(.name) @ $\\(.unit_price/100) = $\\(.amount/100)\")\n' estimate.json\n```\n\nMore recipes, including the company lookup and the full field map, are in\n[`references/recipes.md`](references/recipes.md).\n\n## Is it still awaiting me?\n\n```sh\njq -r '.options.data[] | select(.approval_date == null and .status != \"Declined\")\n       | \"OPEN: \\(.name) $\\(.total_amount/100)\"' estimate.json\n```\n\n## Declining an option\n\nDeclining works from a shell. It is the only mutation that does — and it tells\nthe contractor you are not proceeding, so read the estimate first.\n\n```sh\nOPTION_ID=$(jq -r '.options.data[0].id' estimate.json)   # est_…\n\ncurl -s -X POST \\\n  -H \"Authorization: Token $HCP_TOKEN\" \\\n  --data-urlencode \"estimate_option_uuids[]=$OPTION_ID\" \\\n  https://app.housecallpro.com/api/estimates/estimate_options/customer_declines\n```\n\nNote the asymmetry: **writes put the token in the `Authorization` header**,\nreads put it in the path.\n\n**A 2xx is not proof.** Re-read and check the option actually moved:\n\n```sh\ncurl -s \"https://app.housecallpro.com/alpha/customer_estimates/$HCP_TOKEN\" \\\n  | jq -r '.options.data[] | \"\\(.id) \\(.status) approval_date=\\(.approval_date)\"'\n```\n\n## Approving cannot be scripted\n\n`POST /api/estimates/estimate_options/customer_approvals` requires a\n`response_token` — a **reCAPTCHA v3 token** minted in-page for the action\n`estimates_customer_approvals`. No shell client can produce one, and neither can\nthe fetchproxy bridge (it issues `fetch` calls; it does not run page JS).\n\nOpen the link in a browser and press Approve there. Do not try to work around\nthis: approving is a binding commitment to a quoted price.\n\n## Failure modes\n\n| Symptom | Meaning |\n| --- | --- |\n| `401` / `403` | The link expired or was revoked. Ask your contractor to resend it. |\n| `404` (HTML) | The document was deleted or re-issued; the old link is dead. |\n| `$HCP_TOKEN` is empty or not 129 chars | The short link expired before it could redirect. |\n| A `200` that isn't JSON | Same as above — the link is no longer valid. |\n\n## Invoices\n\n```sh\ncurl -s -H 'Accept: application/json' \\\n  \"https://app.housecallpro.com/api/invoices/consumer/v1/invoices/$HCP_TOKEN\" > invoice.json\n```\n\n**The `v1` segment is required.** `/api/invoices/consumer/invoices/<token>` —\nthe same path without it — returns 404, as do the `/api/v2/consumer/invoices/…`\nand `/api/v2/consumer/sent_invoices/…` paths that also appear in the app's\nJavaScript. Only this one answers.\n\n```sh\njq -r '\"\\(.company_info.name) — invoice #\\(.invoice_number) [\\(.status)]\",\n       \"  subtotal $\\(.subtotal/100)\",\n       \"  tax      $\\((.total - .subtotal)/100)\",\n       \"  total    $\\(.total/100)\",\n       \"  due      $\\(.due_amount/100)\"' invoice.json\n```\n\nTwo things to know: the invoice document has **no line items** (a paid invoice\nrenders as a summary in the portal, and the API returns exactly that), and **no\ntax field** — the tax figure is `total - subtotal`. Money is integer cents here\ntoo. Use `due_amount` rather than `status` to decide whether it is settled.\n\nFile v1.0.2:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"housecallpro-mcp\",\n  \"version\": \"1.0.2\",\n  \"publishedAt\": 1790179117704\n}\n\nFile v1.0.2:references/recipes.md\n\n# Housecall Pro consumer API — recipes\n\nEvery recipe assumes `HCP_TOKEN` holds the 129-character retrieval token, as set\nup in `SKILL.md`. Endpoints marked **unverified** were read out of the SPA's\nJavaScript but never exercised against a real document.\n\n## Field map — `GET /alpha/customer_estimates/$HCP_TOKEN` (verified)\n\nThe envelope nests under `{object, data}` wrappers.\n\n| Path | What it is |\n| --- | --- |\n| `.estimate.data.estimate_number` | Human-facing estimate number |\n| `.estimate.data.estimate_uuid` | `csr_…`, the estimate's id |\n| `.estimate.data.organization_id` | Company UUID — feeds the company lookup |\n| `.estimate.data.customer_approval_mode` | e.g. `single_option` |\n| `.estimate.data.address.data.printable_address` | Service address |\n| `.options.data[]` | One entry per priced option |\n| `.options.data[].id` | `est_…` — **this is what you decline** |\n| `.options.data[].status` | e.g. `Awaiting Approval`, `Declined` |\n| `.options.data[].approval_date` | `null` until approved |\n| `.options.data[].sub_total` / `.total_amount` | **integer cents** |\n| `.options.data[].tax.data.rate` | Fraction (`0.0825`), NOT cents |\n| `.options.data[].tax.data.amount` | **integer cents** |\n| `.options.data[].line_items.data[]` | `name`, `description`, `quantity`, `kind`, `unit_price`, `amount` |\n| `.customer_name`, `.customer_email` | Who it is for |\n| `.message_from_pro` | Free-text note |\n| `.company_name`, `.company_phone_number`, `.company_email`, `.company_website` | The contractor |\n| `.payment_options.can_pay_online` | Whether online payment is offered |\n| `.deposit_requirement` | e.g. `not required` |\n| `.signatures_enabled` | Whether approval demands a signature |\n\n`kind` on a line item is `labor` or `material` — not `service`.\n\n## Totals, correctly scaled\n\n```sh\njq -r '.options.data[]\n  | \"\\(.name): subtotal $\\(.sub_total/100) + tax $\\(.tax.data.amount/100)\"\n  + \" (\\(.tax.data.rate*100)%) = $\\(.total_amount/100)\"' estimate.json\n```\n\n## Line items as TSV\n\n```sh\njq -r '.options.data[].line_items.data[]\n  | [.name, .kind, .quantity, (.unit_price/100), (.amount/100)] | @tsv' estimate.json\n```\n\n## Just the open options and their ids\n\n```sh\njq -r '.options.data[]\n  | select(.approval_date == null and .status != \"Declined\")\n  | \"\\(.id)\\t\\(.name)\\t$\\(.total_amount/100)\"' estimate.json\n```\n\n## The contractor behind it (verified)\n\n```sh\nORG=$(jq -r '.estimate.data.organization_id' estimate.json)\ncurl -s \"https://app.housecallpro.com/alpha/organizations/$ORG\" | jq\n```\n\nReturns `id`, `company_name`, `phone_number`, `email`, `website`, `logo_url`,\n`address`, `default_arrival_window`, `terms_url`, `founding_pro_uuid`. Needs no\nauth header at all.\n\n## Decline (verified shape, from the app's own code)\n\n```sh\ncurl -s -X POST \\\n  -H \"Authorization: Token $HCP_TOKEN\" \\\n  --data-urlencode \"estimate_option_uuids[]=est_XXXX\" \\\n  https://app.housecallpro.com/api/estimates/estimate_options/customer_declines\n```\n\nRepeat `--data-urlencode \"estimate_option_uuids[]=…\"` per option.\n\n## Approve — blocked\n\n```\nPOST /api/estimates/estimate_options/customer_approvals\n  Authorization: Token <token>\n  estimate_option_uuids[]=est_…\n  response_token=<reCAPTCHA v3 token>       <-- cannot be produced outside the page\n  signature[signature], signature[signatory_name], signature[signatory_user_agent]\n```\n\n`estimates_customer_approvals` is the only reCAPTCHA action in the entire\nconsumer app. Approve in a browser.\n\n## Invoices — `GET /api/invoices/consumer/v1/invoices/$HCP_TOKEN` (verified)\n\nAn invoice token is **32 hex characters**, not the estimate's 129. The document\nis flat — no `{object, data}` wrappers.\n\n| Path | What it is |\n| --- | --- |\n| `.invoice_number` | Human-facing invoice number |\n| `.status` | Display string, e.g. `paid` |\n| `.due_amount` | **integer cents** — the field that decides paid-ness |\n| `.subtotal` / `.total` / `.amount` | **integer cents** |\n| `.company_info.{name,phone_number,email,website,organization_uuid}` | The contractor |\n| `.customer.{uuid,email,mobile_number,card_on_file}` | You |\n| `.payment_options.can_pay_online` | Whether it can still be paid online |\n| `.invoice_count` | How many invoices the job carries |\n\nNo line items, and no tax field — tax is `total - subtotal`.\n\n```sh\njq -r '\"#\\(.invoice_number) [\\(.status)] due $\\(.due_amount/100) of $\\(.total/100)\"' invoice.json\n```\n\nThese 404 for an invoice token despite appearing in the bundles — do not use\nthem: `/api/invoices/consumer/invoices/{t}`, `/api/v2/consumer/invoices/{t}`,\n`/api/v2/consumer/sent_invoices/{t}`,\n`/api/invoices/linking/consumer/sources/{t}`,\n`/api/v2/consumer/invoices/{t}/invoice_or_estimate_pdf`.\n`/alpha/jobs/{t}` answers 401 — it wants a different credential.\n\n## Other endpoints (unverified)\n\nRead out of the SPA bundles; shapes unconfirmed.\n\n**Account-level portal (OTP login, spans all documents from one pro)** —\n`/api/customer_portal/request_otp`, `/api/customer_portal/verify_otp`,\n`/api/v2/customer_portal/magic_links`, `/api/v2/customer_portal/organizations`,\n`/api/v2/consumer/user/{log_in,log_out,profile,service_agreements}`\n\n**Jobs / media** — `/alpha/jobs/{id}`, `/api/customer_gallery/{id}`,\n`/api/attachments/customer_gallery/{id}`, `/alpha/after_actions/{id}`\n\n**Service agreements** — `/api/consumer/service_agreement/{id}`,\n`/api/v2/consumer/service_agreement/{id}`\n\n**Communications** — `/communications/consumer/organizations`,\n`/communications/consumer/preferences/{id}`,\n`/communications/consumer/consents/{opt_in,opt_out}`\n\n## Safety\n\n- The retrieval token is a bearer credential. Never commit it, never paste it\n  into a shared transcript, never put it in a URL you log.\n- Declining is not reversible from here.\n- Never attempt to script approval.\n\nFile v1.0.2:skill-card.md\n\n## Description:\n\nReads Housecall Pro customer estimates and invoices from sent portal links using curl, including totals, line items, company details, invoice status, and the documented decline-only mutation path.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nExternal users, developers, and support staff use this skill to inspect Housecall Pro estimates or invoices from a shell when they need structured totals, document status, contractor details, and cautious decline guidance without running the MCP server.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Housecall Pro document links are bearer credentials that can expose estimate or invoice details if shared or logged.\n\nMitigation: Keep tokens in local variables or controlled files, avoid pasting them into shared transcripts, and never commit them.\n\nRisk: The decline request mutates the estimate and tells the contractor the customer is not proceeding.\n\nMitigation: Read the estimate first, decline only documents you were sent and intend to decline, then re-read the estimate to verify the option status.\n\nRisk: Unverified account, communications, jobs, media, or service-agreement endpoints may access broader portal data or require separate authorization.\n\nMitigation: Use only the verified estimate, invoice, organization lookup, and decline flows unless there is explicit authorization and a separate reason.\n\nRisk: Approving an estimate is a binding commitment and cannot be scripted because it requires an in-page reCAPTCHA token.\n\nMitigation: Open the portal link in a browser and approve manually; do not try to bypass the approval flow.\n\n## Reference(s):\n\n- [Housecall Pro consumer API recipes](references/recipes.md)\n- [ClawHub skill page](https://clawhub.ai/chrischall/skills/housecallpro-mcp)\n\n## Skill Output:\n\n**Output Type(s):** [text, markdown, shell commands, configuration, guidance]\n\n**Output Format:** [Markdown with inline shell commands and jq examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [May include curl and jq commands; does not generate credentials.]\n\n## Skill Version(s):\n\n1.0.2 (source: server release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.0.1: 4 files, 6748 bytes\n\nFiles: references/recipes.md (5775b), skill-card.md (2241b), SKILL.md (5867b), _meta.json (135b)\n\nFile v1.0.1:SKILL.md\n\n---\nname: housecallpro\ndescription: >-\n  Read a Housecall Pro estimate or invoice your contractor sent you — line\n  items, totals, tax, what is still owed, the company behind it — from a shell\n  with plain curl, instead of running the housecallpro-mcp server. Use when you want the data without the MCP, in a\n  script, or on a machine where the MCP isn't installed. Covers declining an\n  option, and why approving cannot be scripted.\n---\n\n# Housecall Pro customer portal via curl (no MCP)\n\nThis reads the **customer** side of Housecall Pro: the estimate or invoice a\ncontractor emails or texts you. It is not the Housecall Pro public API — that\none serves the business running on Housecall Pro and needs an API key from\ntheir account.\n\n**No browser bridge is needed.** `app.housecallpro.com` is not bot-walled; a\nplain `curl` gets a `200`. Do not reach for `fpx` here.\n\n## The one thing to know first\n\nThere is no account and no standing link: Housecall Pro sends a separate,\ndisposable link per document, so you work from whichever link you were sent.\n\nYour link is a **bearer credential**. Anyone holding it can read the estimate\nand decline it. Keep it in a variable or a file you control, never in a command\nyou paste into a shared transcript, and never commit it.\n\n## Setup\n\nTwo forms of link exist, and **estimates and invoices use different token\nshapes**: an estimate token is 129 characters (two 64-char hex halves joined by\n`_`), an invoice token is a bare 32-char hex string. The short link redirects to\nwhichever applies:\n\n```sh\n# What your contractor sent (short form)\nSHORT='https://pro.housecallpro.com/mobile_estimate/XXXXXXXXXX'   # or /mobile_invoice/…\n\n# Resolve it to the retrieval token (an estimate's is 129 chars, an invoice's 32)\nHCP_TOKEN=$(curl -sI \"$SHORT\" | tr -d '\\r' | awk 'tolower($1)==\"location:\"{print $2}' | sed 's#.*/##')\n\n# If you already have a client.housecallpro.com/estimates/… or /invoices/… link, just:\n# HCP_TOKEN='<the last path segment>'\n\necho \"${#HCP_TOKEN}\"   # 129 for an estimate, 32 for an invoice\n```\n\n## Read the estimate\n\n```sh\ncurl -s -H 'Accept: application/json' \\\n  \"https://app.housecallpro.com/alpha/customer_estimates/$HCP_TOKEN\" > estimate.json\n```\n\nNo `Authorization` header — reads carry the token in the path.\n\n**Money is integer cents.** `total_amount: 34639` means `$346.39`. Divide by 100\nbefore reporting anything. `tax.rate` is the exception: it is a fraction\n(`0.0825` = 8.25%) and must not be scaled.\n\nA one-line summary:\n\n```sh\njq -r '\n  \"\\(.company_name) — estimate #\\(.estimate.data.estimate_number)\",\n  \"For: \\(.customer_name) at \\(.estimate.data.address.data.printable_address)\",\n  (.options.data[] |\n    \"  \\(.name) [\\(.status)] $\\(.total_amount/100)\"),\n  (.options.data[].line_items.data[] |\n    \"    \\(.quantity) x \\(.name) @ $\\(.unit_price/100) = $\\(.amount/100)\")\n' estimate.json\n```\n\nMore recipes, including the company lookup and the full field map, are in\n[`references/recipes.md`](references/recipes.md).\n\n## Is it still awaiting me?\n\n```sh\njq -r '.options.data[] | select(.approval_date == null and .status != \"Declined\")\n       | \"OPEN: \\(.name) $\\(.total_amount/100)\"' estimate.json\n```\n\n## Declining an option\n\nDeclining works from a shell. It is the only mutation that does — and it tells\nthe contractor you are not proceeding, so read the estimate first.\n\n```sh\nOPTION_ID=$(jq -r '.options.data[0].id' estimate.json)   # est_…\n\ncurl -s -X POST \\\n  -H \"Authorization: Token $HCP_TOKEN\" \\\n  --data-urlencode \"estimate_option_uuids[]=$OPTION_ID\" \\\n  https://app.housecallpro.com/api/estimates/estimate_options/customer_declines\n```\n\nNote the asymmetry: **writes put the token in the `Authorization` header**,\nreads put it in the path.\n\n**A 2xx is not proof.** Re-read and check the option actually moved:\n\n```sh\ncurl -s \"https://app.housecallpro.com/alpha/customer_estimates/$HCP_TOKEN\" \\\n  | jq -r '.options.data[] | \"\\(.id) \\(.status) approval_date=\\(.approval_date)\"'\n```\n\n## Approving cannot be scripted\n\n`POST /api/estimates/estimate_options/customer_approvals` requires a\n`response_token` — a **reCAPTCHA v3 token** minted in-page for the action\n`estimates_customer_approvals`. No shell client can produce one, and neither can\nthe fetchproxy bridge (it issues `fetch` calls; it does not run page JS).\n\nOpen the link in a browser and press Approve there. Do not try to work around\nthis: approving is a binding commitment to a quoted price.\n\n## Failure modes\n\n| Symptom | Meaning |\n| --- | --- |\n| `401` / `403` | The link expired or was revoked. Ask your contractor to resend it. |\n| `404` (HTML) | The document was deleted or re-issued; the old link is dead. |\n| `$HCP_TOKEN` is empty or not 129 chars | The short link expired before it could redirect. |\n| A `200` that isn't JSON | Same as above — the link is no longer valid. |\n\n## Invoices\n\n```sh\ncurl -s -H 'Accept: application/json' \\\n  \"https://app.housecallpro.com/api/invoices/consumer/v1/invoices/$HCP_TOKEN\" > invoice.json\n```\n\n**The `v1` segment is required.** `/api/invoices/consumer/invoices/<token>` —\nthe same path without it — returns 404, as do the `/api/v2/consumer/invoices/…`\nand `/api/v2/consumer/sent_invoices/…` paths that also appear in the app's\nJavaScript. Only this one answers.\n\n```sh\njq -r '\"\\(.company_info.name) — invoice #\\(.invoice_number) [\\(.status)]\",\n       \"  subtotal $\\(.subtotal/100)\",\n       \"  tax      $\\((.total - .subtotal)/100)\",\n       \"  total    $\\(.total/100)\",\n       \"  due      $\\(.due_amount/100)\"' invoice.json\n```\n\nTwo things to know: the invoice document has **no line items** (a paid invoice\nrenders as a summary in the portal, and the API returns exactly that), and **no\ntax field** — the tax figure is `total - subtotal`. Money is integer cents here\ntoo. Use `due_amount` rather than `status` to decide whether it is settled.\n\nFile v1.0.1:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"housecallpro-mcp\",\n  \"version\": \"1.0.1\",\n  \"publishedAt\": 1789964009869\n}\n\nFile v1.0.1:references/recipes.md\n\n# Housecall Pro consumer API — recipes\n\nEvery recipe assumes `HCP_TOKEN` holds the 129-character retrieval token, as set\nup in `SKILL.md`. Endpoints marked **unverified** were read out of the SPA's\nJavaScript but never exercised against a real document.\n\n## Field map — `GET /alpha/customer_estimates/$HCP_TOKEN` (verified)\n\nThe envelope nests under `{object, data}` wrappers.\n\n| Path | What it is |\n| --- | --- |\n| `.estimate.data.estimate_number` | Human-facing estimate number |\n| `.estimate.data.estimate_uuid` | `csr_…`, the estimate's id |\n| `.estimate.data.organization_id` | Company UUID — feeds the company lookup |\n| `.estimate.data.customer_approval_mode` | e.g. `single_option` |\n| `.estimate.data.address.data.printable_address` | Service address |\n| `.options.data[]` | One entry per priced option |\n| `.options.data[].id` | `est_…` — **this is what you decline** |\n| `.options.data[].status` | e.g. `Awaiting Approval`, `Declined` |\n| `.options.data[].approval_date` | `null` until approved |\n| `.options.data[].sub_total` / `.total_amount` | **integer cents** |\n| `.options.data[].tax.data.rate` | Fraction (`0.0825`), NOT cents |\n| `.options.data[].tax.data.amount` | **integer cents** |\n| `.options.data[].line_items.data[]` | `name`, `description`, `quantity`, `kind`, `unit_price`, `amount` |\n| `.customer_name`, `.customer_email` | Who it is for |\n| `.message_from_pro` | Free-text note |\n| `.company_name`, `.company_phone_number`, `.company_email`, `.company_website` | The contractor |\n| `.payment_options.can_pay_online` | Whether online payment is offered |\n| `.deposit_requirement` | e.g. `not required` |\n| `.signatures_enabled` | Whether approval demands a signature |\n\n`kind` on a line item is `labor` or `material` — not `service`.\n\n## Totals, correctly scaled\n\n```sh\njq -r '.options.data[]\n  | \"\\(.name): subtotal $\\(.sub_total/100) + tax $\\(.tax.data.amount/100)\"\n  + \" (\\(.tax.data.rate*100)%) = $\\(.total_amount/100)\"' estimate.json\n```\n\n## Line items as TSV\n\n```sh\njq -r '.options.data[].line_items.data[]\n  | [.name, .kind, .quantity, (.unit_price/100), (.amount/100)] | @tsv' estimate.json\n```\n\n## Just the open options and their ids\n\n```sh\njq -r '.options.data[]\n  | select(.approval_date == null and .status != \"Declined\")\n  | \"\\(.id)\\t\\(.name)\\t$\\(.total_amount/100)\"' estimate.json\n```\n\n## The contractor behind it (verified)\n\n```sh\nORG=$(jq -r '.estimate.data.organization_id' estimate.json)\ncurl -s \"https://app.housecallpro.com/alpha/organizations/$ORG\" | jq\n```\n\nReturns `id`, `company_name`, `phone_number`, `email`, `website`, `logo_url`,\n`address`, `default_arrival_window`, `terms_url`, `founding_pro_uuid`. Needs no\nauth header at all.\n\n## Decline (verified shape, from the app's own code)\n\n```sh\ncurl -s -X POST \\\n  -H \"Authorization: Token $HCP_TOKEN\" \\\n  --data-urlencode \"estimate_option_uuids[]=est_XXXX\" \\\n  https://app.housecallpro.com/api/estimates/estimate_options/customer_declines\n```\n\nRepeat `--data-urlencode \"estimate_option_uuids[]=…\"` per option.\n\n## Approve — blocked\n\n```\nPOST /api/estimates/estimate_options/customer_approvals\n  Authorization: Token <token>\n  estimate_option_uuids[]=est_…\n  response_token=<reCAPTCHA v3 token>       <-- cannot be produced outside the page\n  signature[signature], signature[signatory_name], signature[signatory_user_agent]\n```\n\n`estimates_customer_approvals` is the only reCAPTCHA action in the entire\nconsumer app. Approve in a browser.\n\n## Invoices — `GET /api/invoices/consumer/v1/invoices/$HCP_TOKEN` (verified)\n\nAn invoice token is **32 hex characters**, not the estimate's 129. The document\nis flat — no `{object, data}` wrappers.\n\n| Path | What it is |\n| --- | --- |\n| `.invoice_number` | Human-facing invoice number |\n| `.status` | Display string, e.g. `paid` |\n| `.due_amount` | **integer cents** — the field that decides paid-ness |\n| `.subtotal` / `.total` / `.amount` | **integer cents** |\n| `.company_info.{name,phone_number,email,website,organization_uuid}` | The contractor |\n| `.customer.{uuid,email,mobile_number,card_on_file}` | You |\n| `.payment_options.can_pay_online` | Whether it can still be paid online |\n| `.invoice_count` | How many invoices the job carries |\n\nNo line items, and no tax field — tax is `total - subtotal`.\n\n```sh\njq -r '\"#\\(.invoice_number) [\\(.status)] due $\\(.due_amount/100) of $\\(.total/100)\"' invoice.json\n```\n\nThese 404 for an invoice token despite appearing in the bundles — do not use\nthem: `/api/invoices/consumer/invoices/{t}`, `/api/v2/consumer/invoices/{t}`,\n`/api/v2/consumer/sent_invoices/{t}`,\n`/api/invoices/linking/consumer/sources/{t}`,\n`/api/v2/consumer/invoices/{t}/invoice_or_estimate_pdf`.\n`/alpha/jobs/{t}` answers 401 — it wants a different credential.\n\n## Other endpoints (unverified)\n\nRead out of the SPA bundles; shapes unconfirmed.\n\n**Account-level portal (OTP login, spans all documents from one pro)** —\n`/api/customer_portal/request_otp`, `/api/customer_portal/verify_otp`,\n`/api/v2/customer_portal/magic_links`, `/api/v2/customer_portal/organizations`,\n`/api/v2/consumer/user/{log_in,log_out,profile,service_agreements}`\n\n**Jobs / media** — `/alpha/jobs/{id}`, `/api/customer_gallery/{id}`,\n`/api/attachments/customer_gallery/{id}`, `/alpha/after_actions/{id}`\n\n**Service agreements** — `/api/consumer/service_agreement/{id}`,\n`/api/v2/consumer/service_agreement/{id}`\n\n**Communications** — `/communications/consumer/organizations`,\n`/communications/consumer/preferences/{id}`,\n`/communications/consumer/consents/{opt_in,opt_out}`\n\n## Safety\n\n- The retrieval token is a bearer credential. Never commit it, never paste it\n  into a shared transcript, never put it in a URL you log.\n- Declining is not reversible from here.\n- Never attempt to script approval.\n\nFile v1.0.1:skill-card.md\n\n## Description:\n\nRead Housecall Pro customer estimates and invoices from the shell with curl and jq, including totals, line items, contractor details, and the decline workflow while explaining why approval must happen in the browser.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nExternal users and developers use this skill to inspect Housecall Pro customer portal estimate or invoice links they received, extract money fields and contractor details, and understand which actions can be safely handled from a shell.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Housecall Pro document links act as bearer credentials that can expose estimate or invoice data.\n\nMitigation: Keep tokens and downloaded JSON files out of shared transcripts, logs, and repositories, and use variables or local files under the user's control.\n\nRisk: The skill includes a ready-to-run command that can decline a real contractor estimate and notify the contractor.\n\nMitigation: Review the exact option identifier and intended outcome before running any decline command, then re-read the estimate to confirm the resulting status.\n\nRisk: A shell client cannot approve an estimate because approval requires an in-page reCAPTCHA response token.\n\nMitigation: Handle approvals only in the browser flow and do not attempt to bypass the documented approval control.\n\n## Reference(s):\n\n- [Housecall Pro consumer API recipes](references/recipes.md)\n\n## Skill Output:\n\n**Output Type(s):** [text, markdown, shell commands, code, guidance]\n\n**Output Format:** [Markdown with inline shell and jq code blocks]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [May include curl requests that read customer portal JSON and a POST command to decline an estimate option.]\n\n## Skill Version(s):\n\n1.0.1 (source: server release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.0.0: 4 files, 6728 bytes\n\nFiles: references/recipes.md (5775b), skill-card.md (2236b), SKILL.md (5867b), _meta.json (135b)\n\nFile v1.0.0:SKILL.md\n\n---\nname: housecallpro\ndescription: >-\n  Read a Housecall Pro estimate or invoice your contractor sent you — line\n  items, totals, tax, what is still owed, the company behind it — from a shell\n  with plain curl, instead of running the housecallpro-mcp server. Use when you want the data without the MCP, in a\n  script, or on a machine where the MCP isn't installed. Covers declining an\n  option, and why approving cannot be scripted.\n---\n\n# Housecall Pro customer portal via curl (no MCP)\n\nThis reads the **customer** side of Housecall Pro: the estimate or invoice a\ncontractor emails or texts you. It is not the Housecall Pro public API — that\none serves the business running on Housecall Pro and needs an API key from\ntheir account.\n\n**No browser bridge is needed.** `app.housecallpro.com` is not bot-walled; a\nplain `curl` gets a `200`. Do not reach for `fpx` here.\n\n## The one thing to know first\n\nThere is no account and no standing link: Housecall Pro sends a separate,\ndisposable link per document, so you work from whichever link you were sent.\n\nYour link is a **bearer credential**. Anyone holding it can read the estimate\nand decline it. Keep it in a variable or a file you control, never in a command\nyou paste into a shared transcript, and never commit it.\n\n## Setup\n\nTwo forms of link exist, and **estimates and invoices use different token\nshapes**: an estimate token is 129 characters (two 64-char hex halves joined by\n`_`), an invoice token is a bare 32-char hex string. The short link redirects to\nwhichever applies:\n\n```sh\n# What your contractor sent (short form)\nSHORT='https://pro.housecallpro.com/mobile_estimate/XXXXXXXXXX'   # or /mobile_invoice/…\n\n# Resolve it to the retrieval token (an estimate's is 129 chars, an invoice's 32)\nHCP_TOKEN=$(curl -sI \"$SHORT\" | tr -d '\\r' | awk 'tolower($1)==\"location:\"{print $2}' | sed 's#.*/##')\n\n# If you already have a client.housecallpro.com/estimates/… or /invoices/… link, just:\n# HCP_TOKEN='<the last path segment>'\n\necho \"${#HCP_TOKEN}\"   # 129 for an estimate, 32 for an invoice\n```\n\n## Read the estimate\n\n```sh\ncurl -s -H 'Accept: application/json' \\\n  \"https://app.housecallpro.com/alpha/customer_estimates/$HCP_TOKEN\" > estimate.json\n```\n\nNo `Authorization` header — reads carry the token in the path.\n\n**Money is integer cents.** `total_amount: 34639` means `$346.39`. Divide by 100\nbefore reporting anything. `tax.rate` is the exception: it is a fraction\n(`0.0825` = 8.25%) and must not be scaled.\n\nA one-line summary:\n\n```sh\njq -r '\n  \"\\(.company_name) — estimate #\\(.estimate.data.estimate_number)\",\n  \"For: \\(.customer_name) at \\(.estimate.data.address.data.printable_address)\",\n  (.options.data[] |\n    \"  \\(.name) [\\(.status)] $\\(.total_amount/100)\"),\n  (.options.data[].line_items.data[] |\n    \"    \\(.quantity) x \\(.name) @ $\\(.unit_price/100) = $\\(.amount/100)\")\n' estimate.json\n```\n\nMore recipes, including the company lookup and the full field map, are in\n[`references/recipes.md`](references/recipes.md).\n\n## Is it still awaiting me?\n\n```sh\njq -r '.options.data[] | select(.approval_date == null and .status != \"Declined\")\n       | \"OPEN: \\(.name) $\\(.total_amount/100)\"' estimate.json\n```\n\n## Declining an option\n\nDeclining works from a shell. It is the only mutation that does — and it tells\nthe contractor you are not proceeding, so read the estimate first.\n\n```sh\nOPTION_ID=$(jq -r '.options.data[0].id' estimate.json)   # est_…\n\ncurl -s -X POST \\\n  -H \"Authorization: Token $HCP_TOKEN\" \\\n  --data-urlencode \"estimate_option_uuids[]=$OPTION_ID\" \\\n  https://app.housecallpro.com/api/estimates/estimate_options/customer_declines\n```\n\nNote the asymmetry: **writes put the token in the `Authorization` header**,\nreads put it in the path.\n\n**A 2xx is not proof.** Re-read and check the option actually moved:\n\n```sh\ncurl -s \"https://app.housecallpro.com/alpha/customer_estimates/$HCP_TOKEN\" \\\n  | jq -r '.options.data[] | \"\\(.id) \\(.status) approval_date=\\(.approval_date)\"'\n```\n\n## Approving cannot be scripted\n\n`POST /api/estimates/estimate_options/customer_approvals` requires a\n`response_token` — a **reCAPTCHA v3 token** minted in-page for the action\n`estimates_customer_approvals`. No shell client can produce one, and neither can\nthe fetchproxy bridge (it issues `fetch` calls; it does not run page JS).\n\nOpen the link in a browser and press Approve there. Do not try to work around\nthis: approving is a binding commitment to a quoted price.\n\n## Failure modes\n\n| Symptom | Meaning |\n| --- | --- |\n| `401` / `403` | The link expired or was revoked. Ask your contractor to resend it. |\n| `404` (HTML) | The document was deleted or re-issued; the old link is dead. |\n| `$HCP_TOKEN` is empty or not 129 chars | The short link expired before it could redirect. |\n| A `200` that isn't JSON | Same as above — the link is no longer valid. |\n\n## Invoices\n\n```sh\ncurl -s -H 'Accept: application/json' \\\n  \"https://app.housecallpro.com/api/invoices/consumer/v1/invoices/$HCP_TOKEN\" > invoice.json\n```\n\n**The `v1` segment is required.** `/api/invoices/consumer/invoices/<token>` —\nthe same path without it — returns 404, as do the `/api/v2/consumer/invoices/…`\nand `/api/v2/consumer/sent_invoices/…` paths that also appear in the app's\nJavaScript. Only this one answers.\n\n```sh\njq -r '\"\\(.company_info.name) — invoice #\\(.invoice_number) [\\(.status)]\",\n       \"  subtotal $\\(.subtotal/100)\",\n       \"  tax      $\\((.total - .subtotal)/100)\",\n       \"  total    $\\(.total/100)\",\n       \"  due      $\\(.due_amount/100)\"' invoice.json\n```\n\nTwo things to know: the invoice document has **no line items** (a paid invoice\nrenders as a summary in the portal, and the API returns exactly that), and **no\ntax field** — the tax figure is `total - subtotal`. Money is integer cents here\ntoo. Use `due_amount` rather than `status` to decide whether it is settled.\n\nFile v1.0.0:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"housecallpro-mcp\",\n  \"version\": \"1.0.0\",\n  \"publishedAt\": 1789872601269\n}\n\nFile v1.0.0:references/recipes.md\n\n# Housecall Pro consumer API — recipes\n\nEvery recipe assumes `HCP_TOKEN` holds the 129-character retrieval token, as set\nup in `SKILL.md`. Endpoints marked **unverified** were read out of the SPA's\nJavaScript but never exercised against a real document.\n\n## Field map — `GET /alpha/customer_estimates/$HCP_TOKEN` (verified)\n\nThe envelope nests under `{object, data}` wrappers.\n\n| Path | What it is |\n| --- | --- |\n| `.estimate.data.estimate_number` | Human-facing estimate number |\n| `.estimate.data.estimate_uuid` | `csr_…`, the estimate's id |\n| `.estimate.data.organization_id` | Company UUID — feeds the company lookup |\n| `.estimate.data.customer_approval_mode` | e.g. `single_option` |\n| `.estimate.data.address.data.printable_address` | Service address |\n| `.options.data[]` | One entry per priced option |\n| `.options.data[].id` | `est_…` — **this is what you decline** |\n| `.options.data[].status` | e.g. `Awaiting Approval`, `Declined` |\n| `.options.data[].approval_date` | `null` until approved |\n| `.options.data[].sub_total` / `.total_amount` | **integer cents** |\n| `.options.data[].tax.data.rate` | Fraction (`0.0825`), NOT cents |\n| `.options.data[].tax.data.amount` | **integer cents** |\n| `.options.data[].line_items.data[]` | `name`, `description`, `quantity`, `kind`, `unit_price`, `amount` |\n| `.customer_name`, `.customer_email` | Who it is for |\n| `.message_from_pro` | Free-text note |\n| `.company_name`, `.company_phone_number`, `.company_email`, `.company_website` | The contractor |\n| `.payment_options.can_pay_online` | Whether online payment is offered |\n| `.deposit_requirement` | e.g. `not required` |\n| `.signatures_enabled` | Whether approval demands a signature |\n\n`kind` on a line item is `labor` or `material` — not `service`.\n\n## Totals, correctly scaled\n\n```sh\njq -r '.options.data[]\n  | \"\\(.name): subtotal $\\(.sub_total/100) + tax $\\(.tax.data.amount/100)\"\n  + \" (\\(.tax.data.rate*100)%) = $\\(.total_amount/100)\"' estimate.json\n```\n\n## Line items as TSV\n\n```sh\njq -r '.options.data[].line_items.data[]\n  | [.name, .kind, .quantity, (.unit_price/100), (.amount/100)] | @tsv' estimate.json\n```\n\n## Just the open options and their ids\n\n```sh\njq -r '.options.data[]\n  | select(.approval_date == null and .status != \"Declined\")\n  | \"\\(.id)\\t\\(.name)\\t$\\(.total_amount/100)\"' estimate.json\n```\n\n## The contractor behind it (verified)\n\n```sh\nORG=$(jq -r '.estimate.data.organization_id' estimate.json)\ncurl -s \"https://app.housecallpro.com/alpha/organizations/$ORG\" | jq\n```\n\nReturns `id`, `company_name`, `phone_number`, `email`, `website`, `logo_url`,\n`address`, `default_arrival_window`, `terms_url`, `founding_pro_uuid`. Needs no\nauth header at all.\n\n## Decline (verified shape, from the app's own code)\n\n```sh\ncurl -s -X POST \\\n  -H \"Authorization: Token $HCP_TOKEN\" \\\n  --data-urlencode \"estimate_option_uuids[]=est_XXXX\" \\\n  https://app.housecallpro.com/api/estimates/estimate_options/customer_declines\n```\n\nRepeat `--data-urlencode \"estimate_option_uuids[]=…\"` per option.\n\n## Approve — blocked\n\n```\nPOST /api/estimates/estimate_options/customer_approvals\n  Authorization: Token <token>\n  estimate_option_uuids[]=est_…\n  response_token=<reCAPTCHA v3 token>       <-- cannot be produced outside the page\n  signature[signature], signature[signatory_name], signature[signatory_user_agent]\n```\n\n`estimates_customer_approvals` is the only reCAPTCHA action in the entire\nconsumer app. Approve in a browser.\n\n## Invoices — `GET /api/invoices/consumer/v1/invoices/$HCP_TOKEN` (verified)\n\nAn invoice token is **32 hex characters**, not the estimate's 129. The document\nis flat — no `{object, data}` wrappers.\n\n| Path | What it is |\n| --- | --- |\n| `.invoice_number` | Human-facing invoice number |\n| `.status` | Display string, e.g. `paid` |\n| `.due_amount` | **integer cents** — the field that decides paid-ness |\n| `.subtotal` / `.total` / `.amount` | **integer cents** |\n| `.company_info.{name,phone_number,email,website,organization_uuid}` | The contractor |\n| `.customer.{uuid,email,mobile_number,card_on_file}` | You |\n| `.payment_options.can_pay_online` | Whether it can still be paid online |\n| `.invoice_count` | How many invoices the job carries |\n\nNo line items, and no tax field — tax is `total - subtotal`.\n\n```sh\njq -r '\"#\\(.invoice_number) [\\(.status)] due $\\(.due_amount/100) of $\\(.total/100)\"' invoice.json\n```\n\nThese 404 for an invoice token despite appearing in the bundles — do not use\nthem: `/api/invoices/consumer/invoices/{t}`, `/api/v2/consumer/invoices/{t}`,\n`/api/v2/consumer/sent_invoices/{t}`,\n`/api/invoices/linking/consumer/sources/{t}`,\n`/api/v2/consumer/invoices/{t}/invoice_or_estimate_pdf`.\n`/alpha/jobs/{t}` answers 401 — it wants a different credential.\n\n## Other endpoints (unverified)\n\nRead out of the SPA bundles; shapes unconfirmed.\n\n**Account-level portal (OTP login, spans all documents from one pro)** —\n`/api/customer_portal/request_otp`, `/api/customer_portal/verify_otp`,\n`/api/v2/customer_portal/magic_links`, `/api/v2/customer_portal/organizations`,\n`/api/v2/consumer/user/{log_in,log_out,profile,service_agreements}`\n\n**Jobs / media** — `/alpha/jobs/{id}`, `/api/customer_gallery/{id}`,\n`/api/attachments/customer_gallery/{id}`, `/alpha/after_actions/{id}`\n\n**Service agreements** — `/api/consumer/service_agreement/{id}`,\n`/api/v2/consumer/service_agreement/{id}`\n\n**Communications** — `/communications/consumer/organizations`,\n`/communications/consumer/preferences/{id}`,\n`/communications/consumer/consents/{opt_in,opt_out}`\n\n## Safety\n\n- The retrieval token is a bearer credential. Never commit it, never paste it\n  into a shared transcript, never put it in a URL you log.\n- Declining is not reversible from here.\n- Never attempt to script approval.\n\nFile v1.0.0:skill-card.md\n\n## Description:\n\nRead a Housecall Pro estimate or invoice sent by a contractor, including line items, totals, tax, balances due, and contractor details, from a shell with curl instead of running the housecallpro-mcp server.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nExternal users and developers use this skill to inspect Housecall Pro customer estimate and invoice links with shell commands, summarize payment details, and understand which actions can or cannot be scripted.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Housecall Pro document links function as bearer credentials and may allow anyone holding the link to read an estimate or invoice and, for estimates, decline an option.\n\nMitigation: Keep tokens out of shared transcripts, logs, and commits; use local variables or files under user control.\n\nRisk: The skill includes a live command that can decline a contractor estimate option.\n\nMitigation: Read the estimate first, manually confirm the exact option ID, name, and amount, then re-read the estimate after the request to confirm the final status.\n\nRisk: Some endpoint notes are explicitly unverified or out of scope.\n\nMitigation: Prefer the verified estimate, invoice, organization lookup, and decline examples; treat unverified endpoint notes as references requiring manual review.\n\n## Reference(s):\n\n- [Housecall Pro consumer API recipes](references/recipes.md)\n\n## Skill Output:\n\n**Output Type(s):** [text, markdown, shell commands, guidance]\n\n**Output Format:** [Markdown with inline shell and jq command examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Includes guidance for handling bearer links, reading estimate and invoice JSON, declining options, and avoiding scripted approvals.]\n\n## Skill Version(s):\n\n1.0.0 (source: server release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.","readmeExcerpt":"Skill: housecallpro-mcp Owner: chrischall Summary: 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 cann","codeSnippets":[],"executableExamples":[{"language":"sh","snippet":"# What your contractor sent (short form)\nSHORT='https://pro.housecallpro.com/mobile_estimate/XXXXXXXXXX'   # or /mobile_invoice/…\n\n# Resolve it to the retrieval token (an estimate's is 129 chars, an invoice's 32)\nHCP_TOKEN=$(curl -sI \"$SHORT\" | tr -d '\\r' | awk 'tolower($1)==\"location:\"{print $2}' | sed 's#.*/##')\n\n# If you already have a client.housecallpro.com/estimates/… or /invoices/… link, just:\n# HCP_TOKEN='<the last path segment>'\n\necho \"${#HCP_TOKEN}\"   # 129 for an estimate, 32 for an invoice"},{"language":"sh","snippet":"curl -s -H 'Accept: application/json' \\"},{"language":"sh","snippet":"curl -s -H 'Accept: application/json' \\\n  \"https://app.housecallpro.com/alpha/customer_estimates/$HCP_TOKEN\" > estimate.json"},{"language":"sh","snippet":"jq -r '\n  \"\\(.company_name) — estimate #\\(.estimate.data.estimate_number)\",\n  \"For: \\(.customer_name) at \\(.estimate.data.address.data.printable_address)\",\n  (.options.data[] |\n    \"  \\(.name) [\\(.status)] $\\(.total_amount/100)\"),\n  (.options.data[].line_items.data[] |\n    \"    \\(.quantity) x \\(.name) @ $\\(.unit_price/100) = $\\(.amount/100)\")\n' estimate.json"},{"language":"sh","snippet":"jq -r '.options.data[] | select(.approval_date == null and .status != \"Declined\")\n       | \"OPEN: \\(.name) $\\(.total_amount/100)\"' estimate.json"},{"language":"sh","snippet":"curl -s -X POST \\\n  -H \"Authorization: Token $HCP_TOKEN\" \\\n  --data-urlencode \"estimate_option_uuids[]=$OPTION_ID\" \\"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: housecallpro\ndescription: >-\n  Read a Housecall Pro estimate or invoice your contractor sent you — line\n  items, totals, tax, what is still owed, the company behind it — from a shell\n  with plain curl, instead of running the housecallpro-mcp server. Use when you want the data without the MCP, in a\n  script, or on a machine where the MCP isn't installed. Covers declining an\n  option, and why approving cannot be scripted.\n---\n\n# Housecall Pro customer portal via curl (no MCP)\n\nThis reads the **customer** side of Housecall Pro: the estimate or invoice a\ncontractor emails or texts you. It is not the Housecall Pro public API — that\none serves the business running on Housecall Pro and needs an API key from\ntheir account.\n\n**No browser bridge is needed.** `app.housecallpro.com` is not bot-walled; a\nplain `curl` gets a `200`. Do not reach for `fpx` here.\n\n## The one thing to know first\n\nThere is no account and no standing link: Housecall Pro sends a separate,\ndisposable link per document, so you work from whichever link you were sent.\n\nYour link is a **bearer credential**. Anyone holding it can read the estimate\nand decline it. Keep it in a variable or a file you control, never in a command\nyou paste into a shared transcript, and never commit it.\n\n## Setup\n\nTwo forms of link exist, and **estimates and invoices use different token\nshapes**: an estimate token is 129 characters (two 64-char hex halves joined by\n`_`), an invoice token is a bare 32-char hex string. The short link redirects to\nwhichever applies:\n\n```sh\n# What your contractor sent (short form)\nSHORT='https://pro.housecallpro.com/mobile_estimate/XXXXXXXXXX'   # or /mobile_invoice/…\n\n# Resolve it to the retrieval token (an estimate's is 129 chars, an invoice's 32)\nHCP_TOKEN=$(curl -sI \"$SHORT\" | tr -d '\\r' | awk 'tolower($1)==\"location:\"{print $2}' | sed 's#.*/##')\n\n# If you already have a client.housecallpro.com/estimates/… or /invoices/… link, just:\n# HCP_TOKEN='<the last path segment>'\n\necho \"${#HCP_TOKEN}\"   # 129 for an estimate, 32 for an invoice\n```\n\n## Read the estimate\n\n```sh\ncurl -s -H 'Accept: application/json' \\\n  \"https://app.housecallpro.com/alpha/customer_estimates/$HCP_TOKEN\" > estimate.json\n```\n\nNo `Authorization` header — reads carry the token in the path.\n\n**Money is integer cents.** `total_amount: 34639` means `$346.39`. Divide by 100\nbefore reporting anything. `tax.rate` is the exception: it is a fraction\n(`0.0825` = 8.25%) and must not be scaled.\n\nA one-line summary:\n\n```sh\njq -r '\n  \"\\(.company_name) — estimate #\\(.estimate.data.estimate_number)\",\n  \"For: \\(.customer_name) at \\(.estimate.data.address.data.printable_address)\",\n  (.options.data[] |\n    \"  \\(.name) [\\(.status)] $\\(.total_amount/100)\"),\n  (.options.data[].line_items.data[] |\n    \"    \\(.quantity) x \\(.name) @ $\\(.unit_price/100) = $\\(.amount/100)\")\n' estimate.json\n```\n\nMore recipes, including the company lookup and the full field map, are in\n[`references/recipes.md`](references/recipes.md).\n\n## Is it "},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"housecallpro-mcp\",\n  \"version\": \"1.2.2\",\n  \"publishedAt\": 1791646159740\n}"},{"path":"references/recipes.md","content":"# Housecall Pro consumer API — recipes\n\nEvery recipe assumes `HCP_TOKEN` holds the 129-character retrieval token, as set\nup in `SKILL.md`. Endpoints marked **unverified** were read out of the SPA's\nJavaScript but never exercised against a real document.\n\n## Field map — `GET /alpha/customer_estimates/$HCP_TOKEN` (verified)\n\nThe envelope nests under `{object, data}` wrappers.\n\n| Path | What it is |\n| --- | --- |\n| `.estimate.data.estimate_number` | Human-facing estimate number |\n| `.estimate.data.estimate_uuid` | `csr_…`, the estimate's id |\n| `.estimate.data.organization_id` | Company UUID — feeds the company lookup |\n| `.estimate.data.customer_approval_mode` | e.g. `single_option` |\n| `.estimate.data.address.data.printable_address` | Service address |\n| `.options.data[]` | One entry per priced option |\n| `.options.data[].id` | `est_…` — **this is what you decline** |\n| `.options.data[].status` | e.g. `Awaiting Approval`, `Declined` |\n| `.options.data[].approval_date` | `null` until approved |\n| `.options.data[].sub_total` / `.total_amount` | **integer cents** |\n| `.options.data[].tax.data.rate` | Fraction (`0.0825`), NOT cents |\n| `.options.data[].tax.data.amount` | **integer cents** |\n| `.options.data[].line_items.data[]` | `name`, `description`, `quantity`, `kind`, `unit_price`, `amount` |\n| `.customer_name`, `.customer_email` | Who it is for |\n| `.message_from_pro` | Free-text note |\n| `.company_name`, `.company_phone_number`, `.company_email`, `.company_website` | The contractor |\n| `.payment_options.can_pay_online` | Whether online payment is offered |\n| `.deposit_requirement` | e.g. `not required` |\n| `.signatures_enabled` | Whether approval demands a signature |\n\n`kind` on a line item is `labor` or `material` — not `service`.\n\n## Totals, correctly scaled\n\n```sh\njq -r '.options.data[]\n  | \"\\(.name): subtotal $\\(.sub_total/100) + tax $\\(.tax.data.amount/100)\"\n  + \" (\\(.tax.data.rate*100)%) = $\\(.total_amount/100)\"' estimate.json\n```\n\n## Line items as TSV\n\n```sh\njq -r '.options.data[].line_items.data[]\n  | [.name, .kind, .quantity, (.unit_price/100), (.amount/100)] | @tsv' estimate.json\n```\n\n## Just the open options and their ids\n\n```sh\njq -r '.options.data[]\n  | select(.approval_date == null and .status != \"Declined\")\n  | \"\\(.id)\\t\\(.name)\\t$\\(.total_amount/100)\"' estimate.json\n```\n\n## The contractor behind it (verified)\n\n```sh\nORG=$(jq -r '.estimate.data.organization_id' estimate.json)\ncurl -s \"https://app.housecallpro.com/alpha/organizations/$ORG\" | jq\n```\n\nReturns `id`, `company_name`, `phone_number`, `email`, `website`, `logo_url`,\n`address`, `default_arrival_window`, `terms_url`, `founding_pro_uuid`. Needs no\nauth header at all.\n\n## Decline (verified shape, from the app's own code)\n\n```sh\ncurl -s -X POST \\\n  -H \"Authorization: Token $HCP_TOKEN\" \\\n  --data-urlencode \"estimate_option_uuids[]=est_XXXX\" \\\n  https://app.housecallpro.com/api/estimates/estimate_options/customer_declines\n```\n\nRepeat `--data-urlencode \"estimate_option_uuids[]=…\""},{"path":"skill-card.md","content":"## Description:\n\nGuides customers in reading Housecall Pro estimates and invoices from the shell and declining estimate options without an MCP server.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nCustomers and developers use this skill to inspect contractor estimates, line items, invoice totals, and amounts due with shell commands. It also explains how to decline an estimate option and why approvals must happen in a browser.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Customer links grant access to the document and can authorize a decline if disclosed.\n\nMitigation: Keep links and tokens out of shared logs, transcripts, and repositories; store them only in a controlled variable or file.\n\nRisk: Declining an option can notify the contractor and may not be reversible from the shell.\n\nMitigation: Review the estimate and selected option before declining, then re-read the document to confirm its status.\n\nRisk: An approval commits the customer to a quoted price and cannot be scripted with this skill.\n\nMitigation: Complete approvals in the browser; do not attempt to bypass the approval flow.\n\n## Reference(s):\n\n- [Housecall Pro consumer API recipes](artifact/references/recipes.md)\n- [ClawHub skill release](https://clawhub.ai/chrischall/skills/housecallpro-mcp)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Guidance]\n\n**Output Format:** [Markdown with shell and jq examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Summaries of estimates or invoices may include customer and contractor details; amounts are reported in currency units rather than integer cents.]\n\n## Skill Version(s):\n\n1.2.2 (source: ClawHub release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment."}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":"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. Skill: housecallpro-mcp Owner: chrischall Summary: 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 cann","editorialQuality":{"score":100,"threshold":65,"status":"ready","wordCount":1456,"uniquenessScore":45,"reasons":[]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-11T18:26:57.801Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-11T18:26:57.801Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-11T20:59:17.588Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}