> ## Documentation Index
> Fetch the complete documentation index at: https://docs.asteragents.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Shovels

> Give your agents U.S. building permit, contractor, property and zoning-decision data from Shovels.ai.

## Overview

The Shovels integration gives agents read-only access to Shovels.ai's national
U.S. construction dataset — building permits as filed with local jurisdictions,
the contractors who pulled them, the properties they were pulled on, and the
planning-board decisions that precede them.

It exposes seven tools:

| Tool                         | Purpose                                                                                                                                           | Cost       |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| `shovels_lookup`             | Resolves a place name into the `geo_id` every other tool needs. Also returns the tag vocabulary, field coverage, data freshness and credit usage. | **Free**   |
| `shovels_search_permits`     | Permits by location, work type, value, property and contractor.                                                                                   | Per record |
| `shovels_search_contractors` | Find and vet contractors by the permits they actually pulled.                                                                                     | Per record |
| `shovels_search_properties`  | One row per address, with permit history rolled up.                                                                                               | Per record |
| `shovels_search_decisions`   | Planning and zoning decisions — approvals, rezonings, variances.                                                                                  | Per record |
| `shovels_metrics`            | Pre-aggregated permit volume, job value and durations.                                                                                            | Per record |
| `shovels_get_contacts`       | Named people at a contractor, or residents of an address.                                                                                         | Per record |

## Connecting

Add the API key under **Control Hub → Integrations → Shovels**. Get the key from
[app.shovels.ai](https://app.shovels.ai/profile-settings/) → Account → API Key.

The key is stored per-organization on `connections.shovels.api_key`. There is no
OAuth flow — the key is the credential.

## Credits: Shovels bills per record, not per request

This is the single most important thing to know before turning these tools
loose.

A search that returns 100 permits costs **100 credits**. The API's own default
page size is 50, so one idle question would cost 50 credits. **Our tools default
to `size: 10` instead**, and every response echoes `credits_charged` taken from
the `x-credits-request` response header so spend is visible in the transcript.

Some useful consequences:

* **Zero-result searches are free.** Narrowing filters costs nothing, so it is
  always cheaper to filter tightly and re-run than to fetch wide and post-filter.
* **`shovels_lookup` is entirely free.** Every geography resolver, the tag list,
  coverage, release date and usage check cost zero credits. Resolve liberally.
* **`shovels_metrics` is far cheaper per insight** than counting permits by
  pagination. If the answer is a number rather than a list, use metrics.
* Check remaining budget any time with `shovels_lookup(resource="usage")`.

<Note>
  Shovels' own documentation states that trial keys are metered per **request**
  rather than per record. That was not true in testing — a trial key was billed
  per record like any other. Assume per-record billing everywhere.
</Note>

## Every dollar amount is in cents

Shovels returns all monetary values as **integer cents**, and the money filters
take cents too. This is not documented anywhere; it was established by checking
values against Shovels' own permit descriptions.

| Field                                       | Raw value    | Actual       |
| ------------------------------------------- | ------------ | ------------ |
| `job_value` on a 45-square reroof           | `2561300`    | \$25,613     |
| `property_assess_market_value`              | `88030700`   | \$880,307    |
| `total_job_value`, Austin residential solar | `4403759100` | \$44,037,591 |

So `permit_min_job_value` for "$30,000 and up" is `3000000`, not `30000`. Passed
as `30000` it silently matches everything over $300.

Values are passed through unconverted so the data stays faithful to the source.
Every tool response that carries money includes a `monetary_values` field
restating the rule.

## The geo\_id chain

`shovels_search_permits`, `shovels_search_contractors`,
`shovels_search_decisions` and `shovels_metrics` all **require** a `geo_id`.

`geo_id` accepts:

* a 2-letter state code — `CA`
* a 5-digit ZIP or ZIP+4 — `78717`, `78717-3915`
* an opaque Shovels id — `a4xysKbZwqg` (Austin, TX)

It does **not** accept a city, county or jurisdiction *name*. For those, call
`shovels_lookup` first:

```
shovels_lookup(resource="city", q="Austin, TX")
  -> geo_id "a4xysKbZwqg"

shovels_search_permits(
  geo_id="a4xysKbZwqg",
  permit_from="2025-01-01",
  permit_to="2025-12-31",
  permit_tags=["solar"],
  size=10
)
```

`permit_from` and `permit_to` are required too, and must be `YYYY-MM-DD`. They
bound the search rather than refining it — omitting them is an error, not a
wider search.

### Gotchas worth knowing

* **County names are stored bare.** Shovels holds `Travis, TX`, so
  `"Travis County, TX"` returns nothing at all. `shovels_lookup` strips
  `County`/`Parish`/`Borough` and retries automatically, and tells you in the
  response message when it did.
* **Place names are ambiguous.** `"Austin"` alone matches 14+ places across TX,
  MN, CO, IN and KY. Always include the state.
* **Array filters must be real arrays.** `permit_tags=["solar","roofing"]` works;
  a comma-joined string returns zero results *with no error*. The tools handle
  serialization, so pass arrays and let them.
* **Multiple tags are AND, not OR.** A permit must carry *every* tag listed. In
  Austin for Q1 2025, `solar` alone matched 229 permits and `plumbing` alone
  2,526 — but `["plumbing","solar"]` together matched 2. For "solar or battery",
  run one search per tag and merge.
* **`next_cursor` is non-null even on the last page.** Following it returns an
  empty result set. A page with fewer records than you asked for is the reliable
  end-of-results signal, and the tools say so in their response message.

## Work-type tags

Metrics and most filters key off a fixed 23-value tag vocabulary:

```
addition, adu, bathroom, battery, demolition, electric_meter, electrical,
ev_charger, fire_sprinkler, gas, generator, grading, heat_pump, hvac, kitchen,
new_construction, plumbing, pool_and_hot_tub, remodel, roofing, solar, telecom,
water_heater
```

`shovels_metrics` requires exactly **one** tag — Shovels computes metrics per
work type and has no combined "all permits" rollup, so ask per tag and sum if
you need a total. On the search tools, stacking tags intersects rather than
unions (see above), so add tags to narrow, not to broaden.

## Check coverage before trusting an aggregate

Permit data quality varies sharply by jurisdiction, and the sparse fields are
often the interesting ones. `shovels_lookup(resource="coverage")` reports how
often each field is actually populated:

```
fees                    fill_pct 0.005   (missing)
owner_name              fill_pct 0.068   (missing)
job_value               fill_pct 0.125   (partial)
property_building_area  fill_pct 0.685   (partial)
```

Those are real numbers for Austin, TX in H1 2025. `job_value` is populated on
12.5% of permits there, so "total job value" for that market means "total across
the eighth of permits that declared one" — and filtering on `permit_min_job_value`
silently drops every permit with a null value.

## Personal data

`shovels_get_contacts` is the only Shovels tool that returns personal
information, and it is deliberately separate so it has to be granted to an agent
explicitly rather than being something an agent stumbles into during ordinary
permit research.

* `kind="employees"` — named contacts at a contracting firm: job title,
  seniority, department, business email, LinkedIn.
* `kind="residents"` — current occupants of an address: personal emails, phone,
  LinkedIn, income range, net worth.

Residents are **occupants, not owners**. Compare against `legal_owner` from
`shovels_search_properties` before treating anyone as the owner of record.

Shovels sells employee and resident data separately, so this tool is expected to
return `403` on a plan without it while every other Shovels tool works normally.
That has not been confirmed against a gated key — if such a key returns an empty
list rather than a 403, "no people found" and "not on your plan" will look
identical.

## Typical chains

**Lead generation — find and reach solar installers in a metro**

1. `shovels_lookup(resource="city", q="Austin, TX")` → `geo_id`
2. `shovels_search_contractors(geo_id, permit_from, permit_to, permit_tags=["solar"], contractor_min_total_permits_count=25, include_tallies=true)`
3. `shovels_get_contacts(kind="employees", contractor_id=...)`

**Market sizing — how big is a trade in a county**

1. `shovels_lookup(resource="county", q="Travis, TX")` → `geo_id`
2. `shovels_metrics(entity="county", geo_id, tag="hvac", property_type="residential", granularity="monthly", metric_from, metric_to)`

**Replacement timing — homes due for a new roof**

1. `shovels_lookup(resource="city", q=...)` → `geo_id`
2. `shovels_search_properties(geo_id, permit_tags=["roofing"], property_min_market_value=50000000)` and read `last_date_by_tag.roofing`

**Getting ahead of construction**

1. `shovels_search_decisions(geo_id="TX", decision_from, decision_to, min_project_value=...)` — approvals and rezonings months before any permit exists.

## Data freshness

Shovels publishes in dated releases rather than continuously.
`shovels_lookup(resource="release")` returns the current release date; permits
filed after it are not in the corpus yet.

## Testing

```bash theme={null}
SHOVELS_API_KEY=... npx tsx scripts/test-shovels.ts          # ~15 credits
SHOVELS_API_KEY=... npx tsx scripts/test-shovels.ts --pii    # + personal data
```

The script keeps every billed call at `size` 1–2 on purpose and prints credits
used before and after, so the exact spend of a run is in its output.
