RoofTap Enrichment API
One JSON call. Roof + property + storm data on every address. Same shape every CRM. Pay-as-you-go, never billed for bad reads.
Quickstart
Activate a key in 60 seconds at /integrations/signup. You'll see the key once on the success page, copy it before closing the tab. Every key is prefixed with rt_live_.
curl -X POST https://www.rooftap.app/api/v1/enrich \
-H "X-API-Key: rt_live_abc123..." \
-H "Content-Type: application/json" \
-d '{ "address": "5701 W Loma Ln, Glendale, AZ 85302" }'Authentication
Every request must include the X-API-Key header. Keys are tied to one billing account. Treat them like passwords: do not embed in client-side JavaScript or commit to git.
Lost your key? Email support@rooftap.appfrom the address on file and we'll rotate it.
Tiers - one API, free & paid
Your key's tier decides which fields return. Free keys get the zero-cost layers: storm history, FEMA hazard risk, Census neighborhood, and areactivation flag (when you pass lead_date). Paid keys ($3.95/record, sliding to $1.95 at volume) get everything in free plusthe real-time roof + property record, roof area, facets & pitch, edge measurements, material takeoff, cost estimate, complexity, pool/garage flags, owner & property details, and solar. Premium fields are simply omitted on the free tier; upgrade and they appear with no code change (same key). Every measurementcarries a confidence score, and you're never billed for a low-confidence read. The $3.95/record rate applies only to calls that request a premium layer — a storm-only call (no roof / property / solar / imagery) is free on a paid key too.
Storm-only data
Every enrich response carries a storms block. By default it's just the last event — storms.last_storm (date, type, hail size, wind, distance) plus the derived storms.claim_eligible_window. That's the answer to "when did the last storm hit this address, how big, and is the claim window still open" in one cheap call. The window we search is 2 years, which matches the ~24-month insurance claim window — older events aren't actionable.
Need the full history? Pass include: ["storm_history"] and the block expands to the complete 2-year event list (events[], newest first) plus max_hail_inches, max_wind_mph, lookback_years, and radius_miles.
// DEFAULT — last event only
POST /v1/enrich
{ "address": "5701 W Loma Ln, Glendale, AZ 85302" }
"storms": {
"last_storm": {
"date": "2025-09-18", "type": "hail",
"hail_in": 1.75, "wind_mph": null, "distance_miles": 2.5
},
"claim_eligible_window": {
"event_date": "2025-09-18", "expires": "2027-09-18", "days_remaining": 463
}
}
// OPT IN — full 2-year history
POST /v1/enrich
{ "address": "5701 W Loma Ln, Glendale, AZ 85302",
"include": ["storm_history"] }
"storms": {
"last_storm": { ... },
"claim_eligible_window": { ... },
"events": [
{ "noaa_event_id": "...", "event_type": "Hail", "event_date": "2025-09-18",
"hail_size_inches": 1.75, "wind_speed_mph": null, "city": "Glendale",
"state_abbr": "AZ", "distance_miles": 2.5, "damage_description": null }
],
"max_hail_inches": 1.75, "max_wind_mph": 72,
"lookback_years": 2, "radius_miles": 10
}roof, property, solar, or imagery) — e.g. "include": ["storms"] — is served as a free call: no roof measurement runs and it is never billed, even on a paid key. You get back the zero-cost layers (storms, FEMA risk, Census neighborhood, and a reactivation flag when you pass lead_date), with billable: false. The moment you add a premium layer, the call runs the full measurement and bills the per-record rate.Two more ways to pull storm data at no charge:
- Single address — request storm-only. As above: any key,
include: ["storms"](optionally withstorm_history), unbilled. Premium roof/property/solar fields are simply omitted. - A list of addresses — use bulk reactivation. Screen an aged lead list against NOAA storm history and get back the homeowners worth a call today. See Bulk reactivation.
POST /v1/enrich
The primary endpoint. Returns the full enrichment payload synchronously. Cold call latency (first time we see an address) is typically 4-7 seconds, Solar API + property + storm history fetched in parallel. Cached addresses (same parcel called twice within the 7-day cache TTL) return sub-second. If you need to keep enrichment off your hot path, use the prewarm endpoint.
Request
| Field | Type | Description |
|---|---|---|
| address | string · required | Free-form US address. Examples: "123 Main St, Austin, TX 78701", "5701 W Loma Ln Glendale AZ". |
| lead_id | string | Your internal id for the lead. Echoed back in the response so you can correlate without holding state. |
| lead_date | string (YYYY-MM-DD) | Date the lead was generated. When present, the response adds a reactivation flag, true if a qualifying storm hit since that date. |
| owner_match_name | string | Name on the lead form. We compare against deed-of-record and return property.owner_match. |
| include | string[] | Narrow which blocks come back. Defaults to ["roof", "property", "imagery", "storms", "solar"]. Pass a subset to skip work you don't need. Two storm-specific tokens: storms returns the storms block (just the last event by default), and storm_history expands it to the full 2-year event list — see Storm-only data. A request with no premium layer (no roof / property / solar / imagery) — e.g. ["storms"] — is free on any key (no measurement runs, never billed). Add any premium layer and the call bills the per-record rate. |
POST https://www.rooftap.app/api/v1/enrich
X-API-Key: rt_live_abc123...
Content-Type: application/json
{
"address": "5701 W Loma Ln, Glendale, AZ 85302",
"lead_id": "lead-9821",
"owner_match_name": "Sarah Johnson"
}Response shape
Free keys return storms, risk (FEMA), neighborhood (Census), and reactivation (when lead_date is sent). Paid keys add roof, property, solar, and data_quality. Numeric fields can be null when source data is missing, never 0 as a fallback. The field tables below marked paid are omitted on the free tier.
200 OK
{
"ok": true,
"lead_id": "lead-9821",
"billable": true,
"address": {
"input": "5701 W Loma Ln, Glendale, AZ 85302",
"formatted": "5701 W Loma Ln, Glendale, AZ 85302, USA",
"lat": 33.5601,
"lng": -112.1888,
"place_id": "ChIJ..."
},
"roof": {
"area_sqft": 4003,
"squares": 44,
"predominant_pitch": "4/12",
"complexity": "cut_up",
"num_facets": 9,
"linear_measurements": {
"drip_edge_ft": 457
}
},
"gutter": {
"linear_feet": 127,
"downspout_count_estimate": 4
},
"siding": {
"wall_sqft_estimate": 2540,
"perimeter_ft": 254,
"stories": 1
},
"solar": {
"suitability": "high",
"max_panel_count": 34,
"kw_potential": 13.6,
"annual_kwh_potential": 14820,
"max_array_area_sqft": 612,
"sunshine_hours_per_year": 1820,
"panel_capacity_watts": 400
},
"property": {
"year_built": 1972,
"estimated_roof_age_years": 18,
"owner_match": true,
"owner_occupied": true,
"lot_size_sqft": 7800,
"stories": 1,
"bedrooms": 3,
"bathrooms": 2,
"last_sale_date": "2024-09-12",
"last_sale_price": 412000,
"sold_within_12mo": true
},
"storms": {
"last_storm": {
"date": "2025-09-18",
"type": "hail",
"hail_in": 1.75,
"wind_mph": null,
"distance_miles": 2.5
},
"claim_eligible_window": {
"event_date": "2025-09-18",
"expires": "2027-09-18",
"days_remaining": 463
}
},
"verify_url": "https://www.rooftap.app/r/a1b2c3d4e5f6a7b8",
"data_quality": {
"confidence": "high",
"imagery_quality": "HIGH",
"footprint_match": true
}
}Roof fields
| Field | Type | Description |
|---|---|---|
| roof.area_sqft | number | Total roof area, square feet, computed from Solar API rooftop polygon. |
| roof.squares | number | Roofing squares (area_sqft / 100). Headline number for roofers. |
| roof.predominant_pitch | string | Most-common pitch across roof facets, format "X/12". |
| roof.complexity | enum | One of `simple` | `moderate` | `cut_up`. Drives waste % + labor estimates. |
| roof.num_facets | number | Distinct roof planes detected. Higher = more complex. |
| roof.linear_measurements.drip_edge_ft | number | Building outline perimeter, eaves + rakes summed. Drives drip-edge material quantity. Auto-mode reports ship this field only; per-edge breakdowns (eaves/rakes/ridges/hips/valleys) are surfaced when the contractor manually labels edges on the verify page (HIGH-confidence override). |
Gutter fields
| Field | Type | Description |
|---|---|---|
| gutter.linear_feet | number | Total linear feet of gutter scope. Equals the roof's eave length (which is what gutters attach to). |
| gutter.downspout_count_estimate | number | Rule-of-thumb 1 downspout per 35 ft of gutter, with a floor of 2. Adjust against the actual property if you have it. |
Siding fields
| Field | Type | Description |
|---|---|---|
| siding.wall_sqft_estimate | number | Wall surface area in square feet. Building perimeter × (stories × ~10 ft per story). Coarse but in the right ballpark for quoting. |
| siding.perimeter_ft | number | Building perimeter (the drip-edge length of the roof). Use directly for fascia + trim measurements. |
| siding.stories | number | Above-ground story count from property records. Defaults to 1 when records are missing. |
Solar fields
| Field | Type | Description |
|---|---|---|
| solar.suitability | enum | `high` | `medium` | `low` | `unsuitable`. Single field for lead-aggregator routing. High = $80-150 solar lead; low/unsuitable = roof-only. |
| solar.max_panel_count | number | Maximum panels that fit on the roof per Google Solar API. Buyers use this for system sizing. |
| solar.kw_potential | number | Max system DC capacity in kilowatts. Derived from max_panel_count × panel_capacity_watts. |
| solar.annual_kwh_potential | number | Estimated annual generation in kWh at maximum system size. Drives payback + savings calcs. |
| solar.max_array_area_sqft | number | Usable rooftop area for panels in square feet. |
| solar.sunshine_hours_per_year | number | Annual sun-hours at this latitude/orientation. Lower = lower suitability. |
| solar.panel_capacity_watts | number | Assumed per-panel rating used in the math (Google Solar API default, typically 250-400W). |
Property fields
| Field | Type | Description |
|---|---|---|
| property.year_built | number | Year the structure was built, per county assessor records. |
| property.estimated_roof_age_years | number | Best-effort roof age. Derived from permit history when available; falls back to year_built. |
| property.owner_match | boolean | true when `owner_match_name` from the request matches the deed-of-record. Filters wholesalers + storm-chasers. |
| property.owner_occupied | boolean | true when the property's mailing address matches the property address (homestead heuristic). |
| property.lot_size_sqft | number | Parcel size in square feet, from county records. |
| property.stories | number | Number of above-ground stories. Drives labor + safety equipment cost. |
| property.bedrooms | number | Bedroom count, county-assessor data. |
| property.bathrooms | number | Bathroom count, county-assessor data. |
Storm fields
| Field | Type | Description |
|---|---|---|
| storms.last_storm | object | null | The single most recent NOAA storm event within 10 miles, returned by default on every call: { date (YYYY-MM-DD), type (`hail` | `wind` | `tornado` | `hurricane` | `tropical storm`), hail_in, wind_mph, distance_miles }. `null` when there's no qualifying event in the 2-year window. |
| storms.claim_eligible_window | object | null | Derived window when a homeowner can still file an insurance claim for damage from the last event: { event_date, expires (event_date + 24mo), days_remaining }. `null` when there's no qualifying event. Storm-chaser roofers use this to time outreach. |
| storms.events | array | Full event list, newest first (max 50). Returned ONLY when you pass include=["storm_history"]. Each: { noaa_event_id, event_type, event_date, hail_size_inches, wind_speed_mph, city, state_abbr, distance_miles, damage_description }. |
| storms.max_hail_inches | number | null | Largest hail diameter across the 2-year window, in inches. Included with `storm_history`. `null` if no hail on record. |
| storms.max_wind_mph | number | null | Highest wind gust across the 2-year window, in mph. Included with `storm_history`. `null` if none on record. |
| storms.lookback_years | number | Years of NOAA history searched. Currently 2. Included with `storm_history`. |
| storms.radius_miles | number | Search radius around the subject property, in miles. Currently 10. Included with `storm_history`. |
Contact compliance fields (premium add-on)
| Field | Type | Description |
|---|---|---|
| contact_compliance.tcpa_safe_to_call | boolean | null | Result of federal + state DNC scrub against the contact phone. Only relevant for outbound cold outreach. Inbound consumer-initiated forms are TCPA-exempt. |
| contact_compliance.dnc_listed | boolean | null | True if the phone is on the federal Do-Not-Call list. |
| contact_compliance.last_checked | string | ISO timestamp of the last scrub. Cached 24h to avoid repeat upstream charges. |
Quality + meta
| Field | Type | Description |
|---|---|---|
| verify_url | string | null | Shareable link to a hosted, presentable measurement report for this exact lookup — roof wireframe, measurements, solar, storm history and property details, with a one-click PDF download. Returned on every billable (paid) call; attach it to a CRM deal or job, or hand it to the homeowner. Link lives at `https://www.rooftap.app/r/<token>` and stays live ~30 days. `null` only on the rare best-effort issuance failure. See Measurement report below. |
| data_quality.imagery_quality | enum | `HIGH` | `MEDIUM` | `LOW`. LOW indicates canopy occlusion or stale imagery. |
| data_quality.footprint_match | boolean | true when our roof polygon aligns with the parcel's primary structure footprint. |
| billable | boolean | true when this call counts toward your monthly usage. false on quality-rejects, 4xx errors, and storm-only / free-layer calls (any request with no premium layer is free on any key). |
Measurement report
Every billable call returns a verify_url — a shareable link to a hosted, presentable measurement report for that exact lookup. No need to render the JSON yourself: the page shows the roof wireframe, area / facets / pitch, edge measurements, solar potential, storm history and property details, with a one-click PDF download. It's the same report viewer homeowners see, served at https://www.rooftap.app/r/<token>.
"verify_url": "https://www.rooftap.app/r/a1b2c3d4e5f6a7b8"
Drop it straight onto a CRM deal or job (e.g. a JobTread job or a HubSpot deal property), or hand it to the homeowner. The link stays live for about 30 days — long enough to survive a typical lead-to-close cycle. On a medium/low-confidence read (data_quality.refinement_recommended: true) the page also lets the contractor refine the building selection; high-confidence reads get the link too, with nothing to refine. The field is nullonly on the rare best-effort issuance failure — re-call to get a fresh one. Storm-only / free-layer calls don't run a measurement, so they don't carry a verify_url.
POST /v1/enrich/prewarm
Optional. Hit this at lead intake to kick off the enrichment fetch in the background. Returns 202 in under 50ms, the next/v1/enrich call on the same address (the one that runs when your routing decision happens) lands a warm cache and returns sub-500ms.
Prewarm calls don't bill. Only the real call does.
POST https://www.rooftap.app/api/v1/enrich/prewarm
X-API-Key: rt_live_abc123...
{ "address": "5701 W Loma Ln, Glendale, AZ 85302",
"lead_id": "lead-9821" }
202 Accepted · <50ms
{ "ok": true, "lead_id": "lead-9821",
"message": "Prewarm queued. Hit POST /v1/enrich on the same address in 2-6s for a cache hit." }Bulk reactivation
Run an aged lead list back through NOAA storm history to find the homeowners worth a call today. This is storm-only screening(NOAA storm history + a worth-a-call-today flag + optional ongoing next-storm monitoring), not a full enrich. It's async + batched: you enqueue a job and poll for results (or wait for a webhook). Use it to wake a dormant CRM, not to measure a roof, when you need measurements use /v1/enrich.
POST /v1/reactivate
Enqueue an async batch of up to 1,000 addresses per request. Authenticate with the same X-API-Key header you use for /v1/enrich. Returns 202 immediately with a job_id and a status_url to poll. For a 50k+ list, split into chunks of 1,000 and POST each chunk, every chunk returns its own job_id.
| Field | Type | Description |
|---|---|---|
| addresses | array · required | Up to 1,000 entries. Each entry is either a free-form address string, or an object { address, external_ref? } where external_ref is echoed back so you can correlate to your CRM. |
| monitor | boolean | Default true. When true, every address is enrolled in ongoing next-storm monitoring so you're alerted the next time a qualifying event lands. |
| string | Optional. We email this address a summary when the job finishes. | |
| webhook_url | string | Optional. https only. We POST a completion event here when the job finishes (see below). |
curl -X POST https://www.rooftap.app/api/v1/reactivate \
-H "X-API-Key: rt_live_abc123..." \
-H "Content-Type: application/json" \
-d '{
"addresses": [
"5701 W Loma Ln, Glendale, AZ 85302",
{ "address": "1804 E Vista Ave, Phoenix, AZ 85020", "external_ref": "lead-9821" }
],
"monitor": true,
"email": "ops@yourcompany.com",
"webhook_url": "https://yourcompany.com/hooks/rooftap"
}'
202 Accepted
{
"ok": true,
"job_id": "rjb_7f3c9a2e",
"queued": 2,
"status": "queued",
"status_url": "https://www.rooftap.app/api/v1/reactivate/rjb_7f3c9a2e",
"message": "Batch queued. Poll status_url or wait for the completion webhook."
}The 202 body carries ok, job_id, queued (count accepted this request), status ("queued"), status_url, and a message.
Errors specific to this endpoint:
| HTTP | Code | What to do |
|---|---|---|
| 401 | invalid_key | Header missing or key unrecognized. Same key as /v1/enrich. |
| 402 | upgrade_required | Free keys get 1,000 addresses free for the lifetime of the key. Past that you must upgrade to continue at $0.01/address. |
| 400 | chunk_too_large | More than 1,000 addresses in one request. Split into chunks of 1,000 and POST each. |
| 429 | rate_limited | Max 60 batches per minute per key. Honor Retry-After. |
GET /v1/reactivate/{job_id}
Poll job status and pull results. Authenticate with X-API-Key, you can only read your own jobs. status moves queued → processing → completed (or failed).
| Query param | Type | Description |
|---|---|---|
| include | string | summary returns just the summary block and omits results. |
| status | string | follow_up filters results to hot leads only (the worth-a-call-today addresses). |
| limit | number | Page size. Max 1,000, default 500. |
| offset | number | Result offset for paging. |
curl https://www.rooftap.app/api/v1/reactivate/rjb_7f3c9a2e?status=follow_up \
-H "X-API-Key: rt_live_abc123..."
200 OK
{
"ok": true,
"job_id": "rjb_7f3c9a2e",
"status": "completed",
"summary": {
"total": 2,
"processed": 2,
"follow_ups": 1,
"monitored": 2,
"errors": 0
},
"results": [
{
"address": "5701 W Loma Ln, Glendale, AZ 85302",
"external_ref": null,
"status": "done",
"matched": true,
"last_event": "2026-05-26",
"max_hail_in": 1.75,
"events_5yr": 4,
"worth_following_up": true,
"trigger": { "date": "2026-05-26", "type": "hail", "hail_in": 1.75, "wind_mph": null },
"days_since_last_storm": 8,
"claim_window_days": 365,
"within_claim_window": true,
"priority": 1,
"monitored": true
}
],
"paging": { "limit": 500, "offset": 0, "returned": 1 }
}The response carries ok, job_id, status, a summary block (total, processed, follow_ups, monitored, errors), the results array, and paging (limit, offset, returned).
Reactivation result fields
| Field | Type | Description |
|---|---|---|
| address | string | The address as supplied in the request. |
| external_ref | string | null | Your reference, echoed back when you passed the object form `{ address, external_ref }`. `null` for bare-string entries. |
| status | enum | `done` once processed, or `error` if the address couldn't be checked. Use `worth_following_up` for the hot-lead flag, not this. |
| matched | boolean | true when the address geocoded and matched a parcel with NOAA storm history. |
| last_event | string | null | Date of the most recent qualifying storm event near this address. ISO format (YYYY-MM-DD), `null` if none. |
| max_hail_in | number | null | Largest hail diameter in inches across the screened history. `null` if no hail on record. |
| events_5yr | number | Count of qualifying storm events in the last 5 years within range of this address. |
| worth_following_up | boolean | The headline flag. true when a storm since the lead date makes this a worth-a-call-today reactivation candidate. |
| trigger | object | null | The storm that flagged the address: `{ date, type, hail_in, wind_mph }`. `null` when `worth_following_up` is false. |
| days_since_last_storm | number | null | Whole days since the most recent severe (qualifying) storm. `null` when there's no qualifying storm on record. |
| claim_window_days | number | The address state's property-claim window in days, used to rank this lead. Defaults to 730 (2 years) when the state can't be determined. |
| within_claim_window | boolean | Whether the most recent storm is still inside that claim window. |
| priority | number | null | Call-priority, 1 = call first through 5 = floor (lowest). `null` when there's no qualifying storm. Results are returned sorted by `priority` ascending (1 first). |
| monitored | boolean | true when this address was enrolled in ongoing next-storm monitoring (driven by the request `monitor` flag). |
priority is a call-ordering heuristic, not legal or insurance advice. We rank fresher storms higher so your team works the list top-down. The scale:
- 1 - severe storm 14 days ago or less.
- 2 - 15 to 90 days ago.
- 3 - 91 to 365 days ago.
- 4 - 366 days up to the state's claim window (only reachable in 2-year states).
- 5 - past the state's claim window, or older than 2 years. Still listed, just the lowest rank.
The claim window is per US state (roughly 1 to 2 years) and is a best-effort heuristic. When we can't determine the state we default to 2 years (claim_window_days: 730). Leads are never dropped: anything past the window simply floors to priority 5.
Completion webhook
Optional. If you set webhook_urlon the enqueue call, we POST a completion event there when the job finishes, so you don't have to poll.
POST https://yourcompany.com/hooks/rooftap
Content-Type: application/json
{
"event": "reactivation.completed",
"job_id": "rjb_7f3c9a2e",
"summary": {
"total": 50000,
"processed": 50000,
"errors": 0,
"follow_ups": 3142,
"monitored": 50000
}
}Errors
All errors return JSON with { ok: false, error, message }. The error field is a stable machine-readable code; message is human-readable and may change between releases.
401 Unauthorized · invalid_key
{ "ok": false, "error": "invalid_key",
"message": "X-API-Key header missing or unrecognized." }
422 Unprocessable Entity · address_unresolvable
{ "ok": false, "error": "address_unresolvable",
"message": "Could not geocode the supplied address.",
"billable": false }
429 Too Many Requests · rate_limited
{ "ok": false, "error": "rate_limited",
"message": "Rate limit: 10 rps. Retry after 600ms." }
// Honor the Retry-After response header.| HTTP | Code | What to do |
|---|---|---|
| 400 | invalid_json | Body wasn't valid JSON. Check Content-Type + payload. |
| 400 | address_required | Missing address field. |
| 401 | invalid_key | Header missing or key revoked. Rotate via support. |
| 402 | billing_required | Subscription payment failed. Update card in your billing portal. |
| 422 | address_unresolvable | Geocoder couldn't place the address. Not billed. |
| 422 | no_solar_coverage | Address is outside Solar API coverage. Not billed. |
| 429 | rate_limited | Honor Retry-After header. Default cap 10 rps per key. |
| 500 | internal_error | Our fault. Retry with exponential backoff. Not billed. |
Rate limits
- /v1/enrich - 10 rps per API key. 429 returns a
Retry-Afterheader in seconds. - /v1/enrich/prewarm - 30 rps per API key (separate bucket).
- Need higher caps for an aggregator burst? Email support, we lift to 50+ rps once we see your traffic profile.
Billing
Volume tiers are auto-applied for the entire month based on your final volume. Run 16,000 billable calls in a month and every one that period prices at $2.45, including the first 5,000. Only calls that request a premium layer are billable — storm-only / free-layer calls (billable: false) don't count toward your volume or your bill.
Each billable call is charged independently, including repeat lookups of the same address. Results are licensed for the end customer whose lookup triggered the call: don't store and re-serve our data to other third parties in place of a fresh call. One end-customer lookup, one call.
| Volume | Per call | Notes |
|---|---|---|
| 0 – 5,000 / mo | $3.95 | Entry tier, no minimum. |
| 5,001 – 15,000 / mo | $3.25 | Auto-applied. |
| 15,001 – 30,000 / mo | $2.45 | Auto-applied. |
| 30,000+ / mo | $1.95 | Email support for >100k contracts. |
Quality guarantee
billable: falseand that call doesn't count toward your usage. No tickets, no clawbacks, no end-of-month reconciliation.Changelog
- 2026-06 - Storm block reworked. Responses now return a single
storms.last_stormevent by default (plusclaim_eligible_window); passinclude: ["storm_history"]for the full event list. The history window is now 2 years (was 5), aligned to the claim window. Storm-only calls (any request with no premium layer, e.g.include: ["storms"]) are now free on every key — no measurement runs and they're never billed, even on a paid key. Breaking: the oldstorm.hail_2024/hail_5yr_count/wind_5yr_maxfields are replaced by the structuredstormsblock. - 2026-06 - Bulk reactivation launched. Async batch storm-screening for aged lead lists:
POST /v1/reactivate+GET /v1/reactivate/{job_id}, first 1,000 addresses free, then $0.01/address, with optional ongoing next-storm monitoring and a completion webhook. - 2026-05 - Storm Alerts webhook API launched in free beta. Register addresses, get signed POSTs when qualifying hail/wind events land within 10 mi.
linear_measurementsslimmed todrip_edge_ftonly in auto-mode reports; per-edge fields ship when the report is refined via the on-screen labeling tool. Storm 5-year wind max added. - 2026-04 - Prewarm endpoint launched. Volume tier breakpoints widened.
- 2026-03 -
owner_match_namerequest parameter live. - 2026-02 - v1 GA. Public launch.
60-second self-serve key. Card on file via Stripe.
No NDA. No procurement loop. Cancel any time.