Skip to main content
The properties command group returns one row per property, with that property’s permit history already rolled up onto the record. A single row answers “what has happened at this address” — permit counts, work-type tags, latest activity dates, contractors, job values, and property attributes. Added in CLI v0.8.0; v0.8.1 or newer is required for the permit filters below. Run shovels version to check yours; upgrade with curl -LsSf https://shovels.ai/install.sh | sh.
Properties are currently in beta. Query parameters, response fields, and the absence-trust surface may still change. Treat the response shape as unstable.

Subcommands

Required Scope

Every search needs a scope: --geo-id, --legal-owner, or both. Omitting both fails locally before any API call:

—geo-id

Jurisdiction geo_ids are rejected by this endpoint. Jurisdiction is recorded on only a minority of property records, so scoping to it would silently drop properties that were never permitted. Scope by city, county, state, or address instead.

—legal-owner

Repeat the flag for up to 10 owners. Values are never split on commas, so "SMITH, JOHN" is one owner, not two. Matching is on the owner’s canonical form, so casing variants collapse together.
--legal-owner is the one filter that works nationwide with no location at all.

Permit Filters

There is no --permit-to flag. A property record keeps only the latest permit date per work type, so a closed date window can’t be answered correctly. Passing it fails as an unknown flag. Use shovels permits search for date windows and upper bounds.
An unrecognized tag is an error, not an empty result: the API returns a 422 naming the bad tag. Run shovels tags list --limit all for the canonical set. permits search behaves differently — it returns zero rows for an unknown tag, which is easy to misread as “no such permits.”
Both forms produce the same request. Use --dry-run to confirm what your flags resolved to:
These three filters need CLI v0.8.1 or newer. In v0.8.0 they were single-valued: repeating one silently kept just the last value, and the comma-separated form it documented now fails against the current API. Run shovels version and upgrade if you are below 0.8.1.

Finding unfinaled work

A permit that was pulled but never finaled often signals an unfinished job — for example an installer that went out of business mid-project.
Each row’s last_unfinaled_date_by_tag shows when the unfinished work started:
“Unfinaled” is determined from permit status, not from a missing final_date. See Finding unfinaled permits for why that distinction matters.

Property Attribute Filters

Narrow by what the property is rather than what happened to it.
Attribute data covers roughly 60-70% of properties, and a property with no value for an attribute never matches a range filter on it. Every attribute filter narrows results to the covered set — stacking several can empty a result that the same query returns rows for without them.
story_count is returned on every record but there is no --property-*-story-count filter; the Properties API has no story-count parameter. permits search does filter on story count.

Range validation

Negative and inverted bounds are caught locally, before any API call or credit spend:

Absence Searches

Prefix a tag with - to find properties with no permit of that type on record. Every absence answer carries trust metadata scoring its reliability:
Absence is the highest-value property query and has its own article — see CLI absence searches and trust fields.

properties get

Fetch specific properties by address ID. Accepts 1-50 IDs as positional arguments, all in one request.
ID is a positional argument, not a flag. shovels properties get --id a_123 is wrong; shovels properties get a_123 is right.
A property’s ID is its address geolocation ID — the same id returned by properties search and addresses search:
addresses search exits 1 with No addresses found. when nothing matches, and the empty result then fails properties get with a decode error. In a script, check the resolved ID before passing it on.

How get handles bad IDs

The three failure modes are distinct, and only one of them is survivable:
meta.missing is absent entirely when every requested ID resolved. But one undecodable ID takes down the whole batch:
Validate IDs before batching 50 of them. A single malformed ID costs you the entire request, while a merely-unknown ID costs you nothing.
Trust metadata is a search-only surface: rows returned by properties get carry no trust object, even though the generated schema lists the fields.

Response Fields

shovels schema properties search prints all 45 response fields offline, with types and units, and needs no API key. The rollup fields are the ones with no equivalent in permits search:
apn is on every record so you can join into your own systems, but you can’t search by it: APNs are county-specific, millions collide across counties, and roughly 30% of properties have none. Resolve the address to a geo_id instead.

Dry Run

Add --dry-run to print the resolved HTTP request without calling the API or spending credits — useful for confirming how flags map to query parameters: