Skip to main content

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 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").
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 “30,000andup"is3000000,not30000.Passedas30000itsilentlymatcheseverythingover30,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:
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:
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:
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

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.