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:Connecting
Add the API key under Control Hub → Integrations → Shovels. Get the key from app.shovels.ai → Account → API Key. The key is stored per-organization onconnections.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 tosize: 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_lookupis entirely free. Every geography resolver, the tag list, coverage, release date and usage check cost zero credits. Resolve liberally.shovels_metricsis 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").
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.
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.
So
permit_min_job_value for “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)
shovels_lookup first:
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_lookupstripsCounty/Parish/Boroughand 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,
solaralone matched 229 permits andplumbingalone 2,526 — but["plumbing","solar"]together matched 2. For “solar or battery”, run one search per tag and merge. next_cursoris 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: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:
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.
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 metroshovels_lookup(resource="city", q="Austin, TX")→geo_idshovels_search_contractors(geo_id, permit_from, permit_to, permit_tags=["solar"], contractor_min_total_permits_count=25, include_tallies=true)shovels_get_contacts(kind="employees", contractor_id=...)
shovels_lookup(resource="county", q="Travis, TX")→geo_idshovels_metrics(entity="county", geo_id, tag="hvac", property_type="residential", granularity="monthly", metric_from, metric_to)
shovels_lookup(resource="city", q=...)→geo_idshovels_search_properties(geo_id, permit_tags=["roofing"], property_min_market_value=50000000)and readlast_date_by_tag.roofing
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
size 1–2 on purpose and prints credits
used before and after, so the exact spend of a run is in its output.