Get a Retrofit Estimate from the IncentEdge API
The IncentEdge Estimate API returns a costed, incentive-stacked retrofit estimate for a building: capital cost ranges by scope tier, the federal and state incentives that apply to each tier, and, if you ask for them, ranked financing structures. Every response records the methodology version and data snapshot it came from, so a figure you quote today can be traced later. This walkthrough calls it from Python and JavaScript.
Updated September 10, 2026: An earlier version of this tutorial documented project matching, program browsing, webhook and report endpoints on an api.incentedge.com host. None of those exist. The v1 API is the three endpoints listed below, served from www.incentedge.com/api/v1 and described by the OpenAPI 3.1 spec.
What You Will Build
In this tutorial, you will build a small script that:
- ✓Authenticates with an IncentEdge API key
- ✓Requests an estimate for a building by NYC BBL or by an explicit building profile
- ✓Reads the cost range, incentives and excluded incentives for each scope tier
- ✓Surfaces the consistency warnings and accuracy disclosure that come with every estimate
- ✓Saves the audit artifact for your deal file
Step 1: Get an API Key
API access is included with the Professional and Enterprise plans. Request a key and IncentEdge will issue a test key first. Keys are ie_live_ or ie_test_ followed by 32 hex characters, and the estimate endpoints need the cost:read scope. Store the key as an environment variable, never in source code.
INCENTEDGE_API_KEY=ie_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
INCENTEDGE_BASE_URL=https://www.incentedge.com/api/v1Every request sends the key in the X-API-Key header. The API returns 401 for a missing or invalid key, 403 for a key without the cost:read scope, and 429 when you exceed your rate limit.
Step 2: Request an Estimate
POST /estimate takes exactly one of two inputs: bbl, a 10-digit NYC BBL or a W-prefixed Westchester parcel id that IncentEdge resolves to a building record, or building, an explicit profile in which building_area (gross square feet) is required. The optional tiers array limits the response to the scope tiers you want: quick_wins, code_compliant, high_performance and net_zero_ready.
import os
import httpx
API_KEY = os.environ["INCENTEDGE_API_KEY"]
BASE_URL = os.environ["INCENTEDGE_BASE_URL"]
def get_estimate(body: dict) -> dict:
response = httpx.post(
f"{BASE_URL}/estimate",
headers={"X-API-Key": API_KEY},
json=body,
timeout=30.0,
)
response.raise_for_status()
return response.json()
# By NYC BBL: IncentEdge looks the building up for you
estimate = get_estimate({"bbl": "1008350041"})
# Or with an explicit building profile
estimate = get_estimate({
"building": {
"building_area": 120000,
"year_built": 1962,
"building_class": "multifamily",
"num_floors": 12,
"region": "nyc",
},
"tiers": ["code_compliant", "high_performance"],
"labor_basis": "prevailing",
})
print(estimate["methodology_version"], estimate["generated_at"])const API_KEY = process.env.INCENTEDGE_API_KEY;
const BASE_URL = process.env.INCENTEDGE_BASE_URL;
async function getEstimate(body) {
const response = await fetch(`${BASE_URL}/estimate`, {
method: 'POST',
headers: {
'X-API-Key': API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
if (!response.ok) {
throw new Error(`Estimate API returned ${response.status}: ${await response.text()}`);
}
return response.json();
}
const estimate = await getEstimate({ bbl: '1008350041' });
console.log(estimate.methodology_version, estimate.generated_at);Step 3: Read the Response
The OpenAPI spec describes the full response. The fields you will use most:
tiers: One entry per scope tier, each with a label, cost_breakdown, incentives, excluded_incentives, roi, measures and sensitivity.sensitivity: The cost range for a tier. Read calibratedExpected, not expected, for the likely outcome: expected is the itemized bottom-up total (and the basis that percentage-of-cost incentives are rated against), while calibratedExpected corrects it for the bias measured against the calibration corpus of cost records (some synthetic). bestCase and worstCase bound the range.incentives / excluded_incentives: The programs applied to the tier, and the ones that were considered but not applied. Treat anything in excluded_incentives as unavailable for that tier. Each incentive carries a type that separates tax credits and rebates from financing, so keep financing out of any incentive total.consistency_warnings: Plain-language warnings about the inputs, for example when labor_basis is market but an incentive’s headline rate assumes prevailing wage compliance.measured_accuracy: The backtest accuracy of the cost engine, with a disclosure string you can show your own users.
def summarize(estimate: dict) -> None:
for warning in estimate["consistency_warnings"]:
print(f"warning: {warning}")
for tier in estimate["tiers"]:
s = tier["sensitivity"]
print(f"\n{tier['label']}")
print(f" likely cost: ${s['calibratedExpected']:,.0f} "
f"(range ${s['bestCase']:,.0f} to ${s['worstCase']:,.0f})")
for inc in tier["incentives"]:
print(f" {inc['name']} [{inc['type']}]: ${inc['amount']:,.0f}")
for exc in tier["excluded_incentives"]:
print(f" not applied: {exc['program']} ({exc['reason']})")
print(f"\n{estimate['measured_accuracy']['disclosure']}")
summarize(estimate)Step 4: Save the Audit Artifact
For a memo or an underwriting file, request the audit artifact instead: POST /estimate/artifact (or POST /estimate?artifact=true) with the same body. It adds line-by-line provenance for costs and incentive programs, a reconciliation showing that the provenance lines sum to each tier's total, and a reproducibility block with a SHA-256 hash of the canonical input. The server returns an error rather than an artifact that does not reconcile.
Reproducibility holds for the same methodology version, calibration snapshot and input. A later calibration can change the numbers, which is why the artifact records all three.
import { writeFile } from 'node:fs/promises';
async function saveArtifact(body) {
const response = await fetch(`${BASE_URL}/estimate/artifact`, {
method: 'POST',
headers: {
'X-API-Key': API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
if (!response.ok) {
throw new Error(`Artifact request returned ${response.status}`);
}
const artifact = await response.json();
const hash = artifact.reproducibility.input_hash;
await writeFile(`estimate-${hash}.json`, JSON.stringify(artifact, null, 2));
return hash;
}
const hash = await saveArtifact({ bbl: '1008350041' });
console.log(`Saved estimate-${hash}.json`);The Incentive Catalog for AI Assistants
IncentEdge's verified incentive catalog is also available to AI assistants through the IncentEdge MCP endpoint at https://www.incentedge.com/api/mcp. It covers New York state, NYC and utility programs, plus federal credits, and every amount it returns carries a source URL and a verification date.
Endpoint Reference
| Endpoint | Method | Description |
|---|---|---|
| /estimate | POST | Costed, incentive-stacked retrofit estimate for a building |
| /estimate/artifact | POST | The same estimate as a full audit artifact (equivalent to POST /estimate?artifact=true) |
| /openapi.json | GET | The OpenAPI 3.1 spec. Public, no key required. |
That is the complete v1 surface, relative to https://www.incentedge.com/api/v1. Generate a client from the spec rather than hand-rolling one.
Request an API key
API access is included with the Professional and Enterprise plans. Tell us what you are building and we will issue a test key first.
Request a key