Home  /  Developer API  /  Integration guide

API integration guide

For the engineering team connecting the Incentive Navigator API to your product: how to authenticate, the four calls that take a street address to a calculated incentive amount, and how errors and limits behave. Every request and response shown here was checked against the production API in September 2026.

01 At a glance

One base URL, JSON over HTTPS

Connection details/v1
Base URL
https://api.incentifind.com
Reference
/v1/reference
OpenAPI document
/v1/openapi.json
Authentication
API key in a request header
Field names
snake_case

The interactive reference and the OpenAPI document are public — no key needed — and the OpenAPI document is the authoritative list of endpoints: apart from the reference and the document themselves, a /v1 path it does not list returns 404.

A note on names: the API is served from api.incentifind.com, and its reference page and OpenAPI document still carry the IncentiFind name. They describe the same API as this guide.

02 Authentication

Keys are issued by our team

There is no self-serve sign-up and no separate test or sandbox key. To request a key, use the contact form with the topic API access, and tell us what you are building and which of the calls below you need — each key is scoped to specific endpoints.

A key begins with icf_live_. Send it on every request, in either header:

X-API-Key: icf_live_…
Authorization: Bearer icf_live_…

A request with no key, or with a key we do not recognise, is refused with 401 unauthorized. Keep the key on your servers; do not ship it to a browser or mobile app.

03 Address to amount

Four calls, from a street address to a dollar figure

The examples follow one multifamily property in Houston, Texas, and one CenterPoint Energy program that applies to it. Treat the figures as illustrative: programs and their rules are revised as we update the catalog, so the same request can return different values later.

Step 1  ·  POST /v1/properties/incentives

Find the programs that apply at an address

curl -X POST 'https://api.incentifind.com/v1/properties/incentives' \
  -H "X-API-Key: $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "address": "8000 W Tidwell Rd, Houston, TX 77040",
    "asset_type": "commercial_multi_family"
  }'

asset_type is required because matching is scoped to the kind of building: at this address, commercial_multi_family returns six programs we can calculate and commercial_office returns four, not all of them the same. Leave it out and the response is 422 validation_error, listing the accepted values. The value you send is echoed back in data.property.asset_type.

Response (excerpt)

{
  "data": {
    "property": {
      "formatted_address": "8000 W Tidwell Rd, Houston, TX 77040",
      "county": "Harris",
      "state": "TX",
      "asset_type": "commercial_multi_family",
      …
    },
    "incentives": [
      …
      {
        "incentive_program_id": 219615,
        "admin_name": "CenterPoint Energy",
        "calculation_rules": {
          "has_approved": true,
          "interaction_mode": "calculator",
          "requires_measure_selection": true,
          …
        },
        …
      },
      …
    ],
    "summary": { "total": 157, "computable_programs": 6, … }
  },
  "meta": { "page": 1, "per_page": 50, "total": 157, "total_pages": 4 }
}
  • Pagination. Results come 50 to a page by default. Pass ?page= and ?per_page= on the URL, and stop at meta.total_pages.
  • Most programs do not produce a number. Here 6 of the 157 can be priced. summary.computable_programs counts them, and adding "filters": { "computable_only": true } to the request body returns only those.
  • interaction_mode tells you what a program needs before you ask for a number:
ValueMeaning
calculatorCollect project inputs, then calculate.
menuPresent the priced options; the user chooses before you calculate.
source_onlyWe hold an approved rule set, but none of its rates computes an amount. Show the program and its source.
nullNo approved rule set is in effect for this program. Show the program, not a number.

Step 2  ·  GET /v1/incentives/{id}  ·  optional

Read the program record

Returns the program’s descriptive record — name, administrator, dates, eligibility text, website_url — with the same calculation_rules summary nested under data.calculation_rules.

Step 3  ·  GET /v1/incentives/{id}/calculation-rules

Read how the amount is calculated

curl 'https://api.incentifind.com/v1/incentives/219615/calculation-rules' \
  -H "X-API-Key: $API_KEY"

Response (excerpt)

{
  "data": {
    "incentive_program_id": 219615,
    "rule_set_id": "irs_4e944bbe86",
    "current_rule_set_id": "irs_4e944bbe86",
    "superseded": false,
    "source": {
      "document_url": "https://visionelements.programprocessing.com/framework/CenterPointTX/2026_CSOP_Program_Manual_Final.pdf",
      "document_sha256": "37b323350f0ef6eeff217e03051a8da04cf5302f99be5d3d096e2f25c851e647",
      …
    },
    "interaction_mode": "calculator",
    "requires_measure_selection": true,
    "selection_groups": [
      { "group_id": "centerpoint_csop_measures", "kind": "pick_one", "option_count": 13, … }
    ],
    "measures": [
      …
      {
        "measure_code": "lighting_led",
        "group_kind": "pick_one",
        "alternative_group_id": "centerpoint_csop_measures",
        "rates": [
          {
            "kind": "per_unit",
            "parameters": { "unit": "kW", "amount_micros": 207000000 },
            "required_inputs": ["kilowatts", "is_small_commercial_site", "kwh_saved"],
            "unit_label": "$207.00/kW",
            …
          },
          …
        ]
      },
      …
    ],
    "disclosures": [ … ],
    …
  }
}
  • Rates are in micros — millionths of a dollar. 207000000 is $207.00 per kW; this measure’s energy rate, 60000, is $0.06 per kWh.
  • required_inputs names the inputs a rate needs. Collect them for the measures you will price.
  • Alternatives. Measures with group_kind: "pick_one" that share an alternative_group_id are alternatives, and a calculation needs one of them chosen. selection_groups lists every such group.
  • Sources are cited throughout. source names the rule set’s document by URL and SHA-256 hash; each entry in disclosures — program conditions and terms — carries its own citation, and so does each line of a calculation. Show disclosures alongside any amount.

Step 4  ·  POST /v1/incentives/{id}/calculate

Calculate the amount

curl -X POST 'https://api.incentifind.com/v1/incentives/219615/calculate?rule_set_id=irs_4e944bbe86' \
  -H "X-API-Key: $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "inputs": { "kilowatts": 20, "kwh_saved": 120000, "is_small_commercial_site": false },
    "measure_selections": [
      { "group_id": "centerpoint_csop_measures", "measure_code": "lighting_led" }
    ]
  }'

Response (excerpt)

{
  "data": {
    "incentive_program_id": 219615,
    "rule_set_id": "irs_4e944bbe86",
    "calculation_status": "calculated",
    "estimate": { "amount_cents": 1134000 },
    "breakdown": [
      …
      {
        "measure_code": "lighting_led",
        "status": "applied",
        "contribution_cents": 414000,
        "formula_display": "$207 × kilowatts",
        "citation": {
          "document_url": "https://visionelements.programprocessing.com/framework/CenterPointTX/2026_CSOP_Program_Manual_Final.pdf",
          "document_sha256": "37b323350f0ef6eeff217e03051a8da04cf5302f99be5d3d096e2f25c851e647",
          …
        },
        …
      },
      {
        "measure_code": "lighting_led",
        "status": "applied",
        "contribution_cents": 720000,
        "formula_display": "$0.06 × kwh_saved",
        …
      },
      …
    ],
    "disclosures": [ … ],
    …
  }
}

$11,340.00: 20 kW at $207 plus 120,000 kWh at $0.06. Calculated amounts are integer cents, and estimate.amount_cents is the figure to use. The breakdown lines show how it was built, but they will not always add up to it: a program cap is applied after the lines are summed, and each line and the total are rounded to cents separately. When a cap reduces the total, limitations says so. For example, 6,000 kW on program 218566 returns one line of 19500000 cents but an estimate.amount_cents of 16250000, with a limitation reading “This estimate has been reduced to the program’s stated maximum of $162,500.” formula_display is text for people, not something to parse. With the rule set pinned (below), the identical request returns the same amounts. Freshness metadata can still change: if we detect a change in the program's source, the same rule set is served with rules.stale set to true.

Branch on calculation_status, not the HTTP status. Each of these is returned with HTTP 200:

StatusWhat it means for you
calculatedA complete amount is in estimate.amount_cents.
requires_selectionChoose a measure first. required_selections lists each group and its options; estimate is null.
requires_inputsInputs are missing. blocking_missing_inputs names them.
not_eligibleThe project fails a program condition; eligibility_failures says which. Send 10 kW and 50,000 kWh to this program and you get this status: it has a minimum project size of 15 kW or 100,000 kWh.
not_calculableWe hold no rules that compute an amount for this program; not_calculable_reason says why.

The reference lists one more status, partial: some rates computed and others could not, so treat the amount as incomplete.

Pin the rule set. Passing the rule_set_id you read in step 3 as ?rule_set_id= prices against exactly that rule set. If a later approval has replaced it, the call returns 409 conflict with the current one in error.details.current_rule_set_id — read the rules again, then recalculate.

04 Errors

One error shape on every /v1 call

{
  "error": {
    "code": "unauthorized",
    "message": "Missing API key",
    "details": null
  },
  "request_id": "req_kdzu4SfJJsnTWJNizjEWKQ"
}

Branch on error.code; message is written for people. details carries specifics when there are any — the offending fields on a 422, the current rule set on a 409. Every response carries an x-request-id header, and on errors it matches request_id. Send your own X-Request-ID header and the API uses it instead. Include it when you report a problem.

HTTPerror.codeWhen
400bad_requestThe request body is not valid JSON.
401unauthorizedNo key, or a key we do not recognise.
403forbiddenListed in the reference: the key is valid but not scoped to this endpoint.
404resource_not_foundNo program with that ID — or a path or method that does not exist. /v1/properties/incentives accepts only POST, so a GET there returns 404.
409conflictThe rule_set_id you pinned has been superseded.
422validation_errorThe request failed validation; details names each field and the problem.
429rate_limit_exceeded
quota_exceeded
One of the two limits below.

05 Limits

Two separate limits

Throughput

Responses from the endpoints above carry x-ratelimit-limit (100), x-ratelimit-remaining and x-ratelimit-reset (seconds until the window refills). A body that is not valid JSON (400), a path that does not exist (404) and a throttled request (429 rate_limit_exceeded) come back without them. The window is 60 seconds and is counted per calling IP address and per endpoint, not per key: requests from the same IP share it, including requests with no valid key. Over the limit, the reference documents 429 rate_limit_exceeded with a Retry-After header giving the seconds to wait before retrying.

Call quota

For keys issued with a call cap, the reference documents X-API-Call-Limit, X-API-Call-Remaining and, only on a cap that resets, X-API-Call-Reset. A key without a cap receives none of these headers, so treat all three as optional. When a capped key is used up, the reference documents 429 quota_exceeded, which retrying does not clear: it lifts only when the quota period rolls over or we raise the cap.

06 What else exists

Two more endpoint families, and what is not built

  • POST /v1/normalize/properties is an import helper. Send up to 100 spreadsheet rows with whatever column names you have; it uses a language model to return each row as a project name, address and asset type with a confidence rating. It does not look up incentives. The asset type comes back in its display form, such as Commercial (Multi-Family), which step 1 also accepts.
  • /v1/data-submissions is for partners who send program data to our catalog. It is documented in the reference.
  • Not available today: an endpoint to list or search the catalog (start from an address, step 1), test-mode or sandbox keys, and webhooks or change notifications.

07 Going live

Before production, and when something is wrong

The reference labels the API early access: response shapes may still change. Parse responses leniently, ignoring fields you do not recognise, and tell us before you go to production.

For a key, a question, or a response that looks wrong, use the contact form and choose API access. Include the request_id.

Ready to test it against your own properties?

Tell us what you are building and which markets matter, and we will set up a key.

Request API access