Developers · REST API

Reference · REST

REST API

Resource endpoints for everything a program needs without the app: searching properties, contacts and organizations, unlocking and revealing them, lead delivery, CRM write-back, lists, notes and webhook subscriptions.

Base URL

Base URL
https://app.greenfinch.ai/api/v1

All requests use HTTPS and a bearer API key (Authentication). Request bodies are JSON with Content-Type: application/json. IDs are UUIDs unless an endpoint says otherwise.

Response envelope

Success
{
  "success": true,
  "data": {
    "…": "endpoint-specific"
  },
  "meta": {
    "…": "optional"
  }
}
Error
{
  "success": false,
  "error": "Human-readable message",
  "meta": {
    "code": "MACHINE_CODE",
    "…": "optional detail"
  }
}
  • Check the HTTP status first, then success. data is present only on success.
  • Branch on the machine code, never on the message text. Most endpoint errors carry it in meta.code. Errors raised before the endpoint runs — a missing scope, a plan that does not include the endpoint, a suspended organization, running out of credits — carry it at the top level as code instead. Read meta?.code ?? code.
  • Validation errors (400, 422) usually have only error. The message says which field is wrong.
  • Responses that carry your data are sent with Cache-Control: no-store; do not cache them in shared proxies.

The full list of codes, statuses and what to do about each is on Limits and errors.

Search and read records

These endpoints find properties, contacts and organizations and read their detail. They need the data:read scope (Enterprise). Each one runs the same code as the MCP tool named beside it, so it returns exactly what that tool returns — data is the result that tool produces — with the same territory limits, plan rules and daily limits.

  • Request bodies and query strings are the tool's own arguments. A key the endpoint does not know is rejected with 400 rather than ignored, so a typo fails loudly.
  • A 400 for bad arguments lists each problem in meta.issues as { field, message }.
  • Refusals become HTTP statuses: 402 out of credits or a failed subscription payment, 403 outside your territory or plan, 404 not found, 409 retired, not yet researched, or the same unlock already in progress (with Retry-After: 1), 429 a daily limit (with Retry-After). The code is in meta.code.
  • These endpoints accept organization API keys only. A person's signed-in connection uses MCP instead (ACTING_USER_NOT_SUPPORTED).
400 · unknown or invalid argument
{
  "success": false,
  "error": "Invalid request — Unrecognized key(s) in object: 'adress'",
  "meta": {
    "issues": [
      {
        "field": null,
        "message": "Unrecognized key(s) in object: 'adress'"
      }
    ]
  }
}

Search properties

POST/properties/searchscopedata:read

Find properties by free text (query — an address, city, owner or property name), by location and criteria (filters), or inside a map box (bounds). Send one of the three. MCP twin: greenfinch_search_properties.

Body

NameTypeDescription
queryoptionalstringFree-text search across address, city, owner, name.
boundsoptionalobject—
filtersoptionalobject—
limitoptionalintegerMax results, default 50, max 200.
cursoroptionalstringPagination cursor from a prior `query`-mode call.
offsetoptionalintegerRows to skip in a county/bounds search — page 2 of a ranked list is offset=limit.
sortByoptionalstring (one of 19)Every sort the app's property search offers — works in query mode AND county/bounds mode. Defaults to "relevance". Numeric and date sorts place properties with no value LAST in both directions; the text sorts (commonName, ownerName, address, city, owner) treat a missing value as the highest value, so it sorts last ascending and FIRST descending. "owner" sorts the vendor parcel feed's owner-of-record; "ownerName" sorts the assessor's owner name — different columns. "address" sorts the raw feed address, not the tidied address shown in results. One of: relevance, commonName, lotSqft, buildingSqft, totalUnits, contactCount, landscapableSqft, roofAreaSqft, buildingFootprintSqft, yearBuilt, numFloors, ownerName, propertyValue, updatedAt, lastEnrichedAt, lastSaleDate, address, city, owner.
sortOrderoptional"asc" | "desc"—
verbosityoptional"compact" | "full"compact (default): the decision fields — id, key, address/geo, owner, category, sizes, value, year, teaser. full: adds provenance/plumbing (parcel account numbers, cluster flags, assessor name variants). Rows are ~40% smaller compact; prefer it unless you need the plumbing.

filters takes a location (countyFips, zipCodes, cities or county names) plus attribute filters such as category, building size, year built and value. The full list is on the tool reference.

curl -X POST "https://app.greenfinch.ai/api/v1/properties/search" \
  -H "Authorization: Bearer $GREENFINCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "query": "1200 Commerce Pkwy, Plano, TX",
  "limit": 5
}'
Response · 200 (abbreviated)
{
  "success": true,
  "data": {
    "results": [
      {
        "id": "3f2a9c1e-0000-4000-8000-000000000001",
        "address": "1200 Commerce Pkwy",
        "city": "Plano",
        "state": "TX",
        "zip": "75074",
        "county": "Collin",
        "owner": "Northgate Business Park LLC",
        "commonName": "Northgate Business Park",
        "assetCategory": "Office",
        "buildingSqft": 96000,
        "yearBuilt": 2004,
        "enrichmentStatus": "completed"
      }
    ],
    "has_more": false,
    "next_cursor": null,
    "sorted_by": {
      "field": "relevance",
      "direction": "desc"
    },
    "searched_scope": {
      "scoped": true,
      "countyCount": 2,
      "states": [
        "TX"
      ]
    }
  }
}
Criteria search
curl -X POST "https://app.greenfinch.ai/api/v1/properties/search" \
  -H "Authorization: Bearer $GREENFINCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "filters": {
    "countyFips": [
      "48085"
    ],
    "categories": [
      "Office"
    ],
    "minBuildingSqft": 20000
  },
  "sortBy": "buildingSqft",
  "sortOrder": "desc",
  "limit": 50
}'
  • Free-text searches page with cursor (pass back next_cursor). Location and map searches page with offset and also return total_matching, next_offset and a category_breakdown.
  • Attribute filters without a location are refused (MISSING_QUERY_BOUNDS_OR_COUNTY); free text combined with filters is refused (AMBIGUOUS_SEARCH_MODE).
  • Searching never costs credits and never returns contact details.

Get property detail

GET/properties/{id}/detailscopedata:read

The full property record with its research and contacts. id is the property ID or property key. MCP twin: greenfinch_get_property. (The Team-plan GET /properties/{id} returns the record without contacts.)

curl "https://app.greenfinch.ai/api/v1/properties/3f2a9c1e-0000-4000-8000-000000000001/detail" \
  -H "Authorization: Bearer $GREENFINCH_API_KEY"
Response · 200 (abbreviated)
{
  "success": true,
  "data": {
    "property": {
      "id": "3f2a9c1e-0000-4000-8000-000000000001",
      "address": "1200 Commerce Pkwy",
      "city": "Plano",
      "state": "TX",
      "assetCategory": "Office",
      "buildingSqft": 96000,
      "recordOwner": "NORTHGATE COMMERCE LP",
      "beneficialOwner": "Northgate Holdings Group",
      "managementCompany": "Lakeside Property Services",
      "aiRationale": "Four-story multi-tenant office building on a landscaped campus…",
      "…": "…"
    },
    "contacts": [
      {
        "id": "3f2a9c1e-0000-4000-8000-000000000011",
        "fullName": "Dana Whitfield",
        "title": "Director of Facilities",
        "employerName": "Lakeside Property Services",
        "email": "dana.whitfield@example.com",
        "phone": "(214) 555-0142",
        "phoneLabel": "Direct",
        "linkedinUrl": "https://www.linkedin.com/in/example-dana-whitfield",
        "role": "property_manager",
        "relationshipStatus": "active",
        "revealed": true,
        "…": "…"
      }
    ],
    "contactsSummary": null,
    "researchRevealed": true,
    "researchState": "revealed",
    "revealSource": "paid",
    "coveredByPlan": true
  }
}
  • researchState is revealed, locked (unlock to read ownership, management, the write-up and contacts; contactsSummary then lists the title of each contact and which details exist) or none (not researched yet).
  • A contact shown only because your subscription covers the county counts toward the daily contact-reveal limit. When that limit is used up the contact comes back without details and with revealCapExceeded: true.
  • Contacts carry no confidence scores and no photoUrl. Six fields — nameConfidence, emailConfidence, phoneConfidence, aiPhoneConfidence, titleConfidence, linkedinConfidence — and photoUrl were removed from this endpoint and its MCP twin on 14 September 2026, the day after the endpoint shipped. They are unreviewed provider signals about a real person, which the app shows a human alongside everything else on the screen and which an API response invites a program to treat as fact.
StatusCodeWhen
403OUT_OF_TERRITORYThe property is outside your service area; meta.reason is territory or subscription.
404NOT_FOUNDNo such property.
409PROPERTY_RETIREDThe property was merged; meta.successorPropertyId names the new ID.

Search contacts by name or employer

GET/contacts/searchscopedata:read

Find people by part of their name or their employer's name. Returns who they are, never their contact details. MCP twin: greenfinch_find_contacts.

Query parameters

NameTypeDescription
queryrequiredstringName or employer fragment, min 2 chars.
limitoptionalintegerMax results, default 10, max 50.
curl "https://app.greenfinch.ai/api/v1/contacts/search?query=Lakeside%20Property&limit=10" \
  -H "Authorization: Bearer $GREENFINCH_API_KEY"
Response · 200
{
  "success": true,
  "data": {
    "contacts": [
      {
        "id": "3f2a9c1e-0000-4000-8000-000000000201",
        "name": "Morgan Ellis",
        "title": "Regional Property Manager",
        "employer": "Lakeside Property Services",
        "location": "Dallas, TX"
      }
    ],
    "note": null
  }
}

Results are not ranked and there is no total; narrow the query if you expect more than limit matches. When nothing matches, note explains why.

StatusCodeWhen
400—A parameter is unknown, invalid, or given more than once (for example ?query=a&query=b); meta.issues names each field.

Browse contacts

POST/contacts/browsescopedata:read

List decision-makers by role, title, employer and county, with paging and a total. Rows carry role, seniority and which details exist — not names or contact details. MCP twin: greenfinch_browse_contacts.

Body (every field optional)

NameTypeDescription
roleTypeoptionalstringContact role-type taxonomy key (see contact-role-taxonomy).
titleTextoptionalstringSubstring match on the contact's title.
countyFipsoptionalstring[]5-digit county FIPS codes, matched via the contact's attached properties.
revealedStateoptional"revealed" | "locked" | "any"Default any.
employerTextoptionalstringSubstring match on the contact's employer name.
organizationIdoptionalstringOrganization UUID: only that firm's people (a current employment record at the firm, or, with marketCountyFips, anyone the market ranking ties to the firm).
marketCountyFipsoptionalstring5-digit county FIPS, with organizationId: mark and rank the firm's people Greenfinch already knows in this county's market.
sortoptional"propertyCount" | "title" | "employerName" | "marketRank"Default employerName. marketRank needs organizationId and marketCountyFips: strongest market evidence first, then everyone else.
limitoptionalintegerMax rows, default 25, max 200.
offsetoptionalintegerRows to skip.
changedKindsoptional"contact_departure" | "contact_title_change" | "contact_employer_change"[]Only people with a recorded change of one of these kinds: contact_departure (the person left the employer we showed for them), contact_title_change (their title changed, seen on their own public listing or our re-check of their profile), contact_employer_change (our re-check of their profile shows a different employer). Combine with changedWithinDays; call greenfinch_get_changes for the evidence. Available once change filters (signals) are switched on for Greenfinch; until then a call that passes this is refused with SIGNALS_NOT_ENABLED, never run without it.
changedWithinDaysoptionalintegerOnly changes the product detected within this many days (1 to 3650). Alone, it matches any of the kinds above. Available once change filters (signals) are switched on for Greenfinch; until then a call that passes this is refused with SIGNALS_NOT_ENABLED, never run without it.
curl -X POST "https://app.greenfinch.ai/api/v1/contacts/browse" \
  -H "Authorization: Bearer $GREENFINCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "roleType": "property_management",
  "countyFips": [
    "48085"
  ],
  "revealedState": "locked",
  "sort": "propertyCount",
  "limit": 25
}'
Response · 200
{
  "success": true,
  "data": {
    "contacts": [
      {
        "contact_id": "3f2a9c1e-0000-4000-8000-000000000041",
        "title": "Senior Property Manager",
        "role_type": "property_management",
        "seniority": "manager",
        "employer_name": "Northgate Business Park LLC",
        "property_count_in_territory": 5,
        "revealed": false,
        "email_validated": true,
        "has_phone": true
      }
    ],
    "total_matching": 1
  }
}

Get a contact

GET/contacts/{id}scopedata:read

One contact and the properties they are attached to. Name, email, phone and LinkedIn are included only once your organization has revealed the contact (or unlocked one of their properties); otherwise the response carries reveal_price_credits. MCP twin: greenfinch_get_contact.

curl "https://app.greenfinch.ai/api/v1/contacts/3f2a9c1e-0000-4000-8000-000000000041" \
  -H "Authorization: Bearer $GREENFINCH_API_KEY"
Response · 200 (not yet revealed)
{
  "success": true,
  "data": {
    "contact_id": "3f2a9c1e-0000-4000-8000-000000000041",
    "title": "Senior Property Manager",
    "role_type": "property_management",
    "seniority": "manager",
    "employer_name": "Northgate Business Park LLC",
    "email_validated": true,
    "has_phone": true,
    "revealed": false,
    "relationships": [
      {
        "in_your_territory": true,
        "property_id": "3f2a9c1e-0000-4000-8000-000000000001",
        "address": "1200 Commerce Pkwy",
        "city": "Plano",
        "state": "TX"
      }
    ],
    "reveal_price_credits": 5
  }
}

Once revealed, the response adds fullName, email, phone, phoneLabel, phoneFallbackFromEmployer and linkedinUrl. A contact covered only by your subscription counts toward the daily contact-reveal limit; 429 when it is used up.

Get a contact's properties

GET/contacts/{id}/relationshipsscopedata:read

Every property the contact is attached to. A property outside your territory appears only as its state and county. MCP twin: greenfinch_get_contact_relationships.

curl "https://app.greenfinch.ai/api/v1/contacts/3f2a9c1e-0000-4000-8000-000000000201/relationships" \
  -H "Authorization: Bearer $GREENFINCH_API_KEY"
Response · 200
{
  "success": true,
  "data": {
    "relationships": [
      {
        "in_your_territory": true,
        "property_id": "3f2a9c1e-0000-4000-8000-000000000001",
        "address": "1200 Commerce Pkwy",
        "city": "Plano",
        "state": "TX"
      },
      {
        "in_your_territory": false,
        "state": "OK",
        "county_fips": "40109"
      }
    ]
  }
}

Browse organizations

POST/organizations/browsescopedata:read

The owner and property-management firms in your territory, filterable by role, county and name, with paging and a total. MCP twin: greenfinch_browse_organizations.

Body (every field optional)

NameTypeDescription
roleoptionalstring (one of 5)One of: owner, property_manager, operator, tenant, any.
countyFipsoptionalstring[]5-digit county FIPS codes.
queryoptionalstringSubstring match on the firm's name.
minPropertiesoptionalintegerOnly firms with at least this many in-territory properties.
sortoptional"propertyCount" | "name"Default name.
limitoptionalintegerMax rows, default 25, max 200.
offsetoptionalintegerRows to skip.
curl -X POST "https://app.greenfinch.ai/api/v1/organizations/browse" \
  -H "Authorization: Bearer $GREENFINCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "role": "owner",
  "countyFips": [
    "48085"
  ],
  "minProperties": 2,
  "sort": "propertyCount"
}'
Response · 200
{
  "success": true,
  "data": {
    "organizations": [
      {
        "organization_id": "3f2a9c1e-0000-4000-8000-000000000301",
        "name": "Northgate Business Park LLC",
        "domain": "northgate.example.com",
        "roles": [
          "owner",
          "property_manager"
        ],
        "property_count_in_territory": 7,
        "researched": true
      }
    ],
    "total_matching": 1,
    "scope": {
      "scoped": true,
      "countyCount": 4,
      "states": [
        "TX"
      ]
    }
  }
}

Get an organization

GET/organizations/{id}scopedata:read

One firm: identity, roles, up to 10 of its properties in your territory, and how many contacts it has by role (never names). MCP twin: greenfinch_get_organization.

curl "https://app.greenfinch.ai/api/v1/organizations/3f2a9c1e-0000-4000-8000-000000000301" \
  -H "Authorization: Bearer $GREENFINCH_API_KEY"
Response · 200
{
  "success": true,
  "data": {
    "organization": {
      "organization_id": "3f2a9c1e-0000-4000-8000-000000000301",
      "name": "Northgate Business Park LLC",
      "domain": "northgate.example.com",
      "roles": [
        "owner",
        "property_manager"
      ],
      "socials": {
        "linkedin": "northgate-business-park-example",
        "twitter": null,
        "facebook": null,
        "crunchbase": null
      }
    },
    "property_count_in_territory": 7,
    "researched_property_count": 5,
    "revealed_property_count": 2,
    "properties": [
      {
        "property_id": "3f2a9c1e-0000-4000-8000-000000000001",
        "address": "1200 Commerce Pkwy",
        "city": "Plano",
        "state": "TX",
        "role": "owner"
      }
    ],
    "properties_truncated": false,
    "contacts_by_role": [
      {
        "role_type": "property_management",
        "count": 4
      }
    ]
  }
}

properties_truncated is true when the firm has more than 10 visible properties; list them all with the next endpoint.

Get an organization's properties

GET/organizations/{id}/propertiesscopedata:read

Every property the firm owns or manages, with its role and how the link was established. Properties outside your territory appear only as state and county. MCP twin: greenfinch_get_organization_properties.

curl "https://app.greenfinch.ai/api/v1/organizations/3f2a9c1e-0000-4000-8000-000000000301/properties" \
  -H "Authorization: Bearer $GREENFINCH_API_KEY"
Response · 200
{
  "success": true,
  "data": {
    "organization": {
      "id": "3f2a9c1e-0000-4000-8000-000000000301",
      "name": "Northgate Business Park LLC"
    },
    "properties": [
      {
        "in_your_territory": true,
        "property_id": "3f2a9c1e-0000-4000-8000-000000000001",
        "address": "1200 Commerce Pkwy",
        "city": "Plano",
        "state": "TX",
        "role": "owner",
        "attributionType": "county_record"
      },
      {
        "in_your_territory": false,
        "state": "OK",
        "county_fips": "40109",
        "role": "owner",
        "attributionType": "county_record"
      }
    ]
  }
}

Unlock and reveal

These endpoints spend credits and need the data:unlock scope (Enterprise). Both are safe to repeat: something your organization already unlocked answers alreadyRevealed: true with creditsCharged: 0, and two simultaneous requests for the same item are charged once: while the first is still being processed, the second answers 409 ACTION_IN_PROGRESS with Retry-After: 1 and charges nothing. Retry it; if the first finished, the repeat is free.

Unlock a property

POST/properties/{id}/unlockscopedata:unlock

Unlocks a researched property's full research and every contact attached to it, for 10 credits — free when your subscription covers its county. No body. id is the property ID or property key. MCP twin: greenfinch_reveal_property.

curl -X POST "https://app.greenfinch.ai/api/v1/properties/3f2a9c1e-0000-4000-8000-000000000001/unlock" \
  -H "Authorization: Bearer $GREENFINCH_API_KEY"
Response · 200
{
  "success": true,
  "data": {
    "alreadyRevealed": false,
    "creditsCharged": 10
  }
}

The response confirms the unlock only; read the contacts with GET /properties/{id}/detail.

StatusCodeWhen
402INSUFFICIENT_CREDITSNot enough credits; meta.required and meta.available give the numbers.
402PAYMENT_FAILEDYour subscription payment failed, so credits cannot be spent. Nothing was charged; an admin updates the payment method on the Billing page.
403OUT_OF_TERRITORYThe property is outside your service area.
404NOT_FOUNDNo such property.
409ACTION_IN_PROGRESSAnother request is already unlocking this property. Nothing was charged; retry after Retry-After (1 second).
409NOT_RESEARCHEDThe property has no research to unlock yet.
409PROPERTY_RETIREDThe property was merged; use meta.successorPropertyId.

Reveal a contact

POST/contacts/{id}/revealscopedata:unlock

Reveals one contact's name, email, phone and LinkedIn for 5 credits. Free when the contact is already revealed, belongs to a property you unlocked, or is covered by your subscription. No body. MCP twin: greenfinch_reveal_contact.

curl -X POST "https://app.greenfinch.ai/api/v1/contacts/3f2a9c1e-0000-4000-8000-000000000201/reveal" \
  -H "Authorization: Bearer $GREENFINCH_API_KEY"
Response · 200
{
  "success": true,
  "data": {
    "contact": {
      "id": "3f2a9c1e-0000-4000-8000-000000000201",
      "fullName": "Morgan Ellis",
      "email": "morgan.ellis@example.com",
      "phone": "(214) 555-0167",
      "phoneLabel": "Mobile",
      "phoneFallbackFromEmployer": false,
      "linkedinUrl": "https://www.linkedin.com/in/example-morgan-ellis",
      "title": "Regional Property Manager",
      "employerName": "Lakeside Property Services"
    },
    "creditsCharged": 5,
    "alreadyRevealed": false
  }
}
  • phoneLabel is Mobile, Direct, Office or null; phoneFallbackFromEmployer: true means the number is the main line at the employer.
  • Paid reveals, and free reveals covered only by your subscription, count toward the daily contact-reveal limit.
StatusCodeWhen
402INSUFFICIENT_CREDITSNot enough credits; meta.required and meta.available give the numbers.
402PAYMENT_FAILEDYour subscription payment failed, so credits cannot be spent. Nothing was charged.
404NOT_FOUNDNo such contact, or it is not available to your organization.
409ACTION_IN_PROGRESSAnother request is already revealing this contact. Nothing was charged; retry after Retry-After (1 second).
429DAILY_CAP_EXCEEDEDThe organization's daily contact-reveal limit is used (CREDENTIAL_DAILY_CAP_EXCEEDED for this key's). Wait for Retry-After.

Account

Check a key

GET/mescopeany live key

Confirms the key authenticates and returns what it can do. No scope is needed, so it is the right call for a connection test.

curl "https://app.greenfinch.ai/api/v1/me" \
  -H "Authorization: Bearer $GREENFINCH_API_KEY"
Response · 200
{
  "success": true,
  "data": {
    "org_id": "org_example000000000001",
    "credential_id": "3f2a9c1e-0000-4000-8000-000000000901",
    "scopes": [
      "events:subscribe",
      "leads:read_single",
      "pipeline:write"
    ],
    "label": "CRM sync — production (gfk_Xk2pQ9aB)"
  }
}

Response fields

NameTypeDescription
org_idrequiredstringYour organization's ID.
credential_idrequiredstringThis key's ID.
scopesrequiredstring[]Scopes granted to the key.
labelrequiredstringThe key's name and its first 12 characters, for display.

Get account status

GET/accountscopeaccount:read

Credits, lead-export allowance, licensed coverage and plan features — the numbers to check before an automation spends anything.

curl "https://app.greenfinch.ai/api/v1/account" \
  -H "Authorization: Bearer $GREENFINCH_API_KEY"
Response · 200
{
  "success": true,
  "data": {
    "credit_balance": {
      "total": 4210,
      "included": 3000,
      "purchased": 1000,
      "trial": 0,
      "enterprise_pool": 210
    },
    "egress_quota": {
      "billed_via": "quota",
      "used_this_period": 132,
      "max_exports_per_month": 500,
      "max_rows_per_month": 500,
      "period_start": "2026-09-01T00:00:00.000Z"
    },
    "entitlement": {
      "nationwide": false,
      "state_abbrs": [
        "TX"
      ],
      "county_fips": []
    },
    "serviceable_area": {
      "whole_state_abbrs": [
        "TX"
      ],
      "county_fips": []
    },
    "tier": {
      "can_run_enrichments": true,
      "can_use_pipeline": true,
      "can_use_lists": true,
      "can_export_full_crm": true
    }
  }
}

Response fields

NameTypeDescription
credit_balancerequiredobjectSpendable credits: total and its parts (included with the plan, purchased, trial, enterprise_pool).
egress_quotarequiredobjectThe free monthly lead-export allowance. billed_via is quota while the next export is still free and credit once it would cost a credit. See lead bundle billing.
entitlementrequiredobjectThe states and counties your subscription licenses (or nationwide).
serviceable_arearequiredobjectThe area your organization has switched on — what keys can see.
tierrequiredobjectPlan features that gate other calls.

Leads

A lead is a property your team (or an agent) qualified in the Greenfinch pipeline. Each qualification produces one event; a CRM receives it by webhook or by polling the feed, then fetches the full lead bundle.

Poll qualified leads

GET/leadsscopeevents:subscribe

A metadata-only feed of qualification events, oldest first — the polling alternative to a webhook. It never returns lead data and never costs anything; fetch each bundle with GET /leads/{id}.

Query parameters

NameTypeDescription
updated_sinceoptionalISO-8601 timestampOnly events recorded at or after this time. Ignored when cursor is sent.
cursoroptionalstringThe next_cursor from the previous page. Opaque; do not build it yourself.
limitoptionalintegerPage size, default and maximum 100. Larger values are reduced to 100.
curl "https://app.greenfinch.ai/api/v1/leads?updated_since=2026-09-01T00%3A00%3A00Z&limit=50" \
  -H "Authorization: Bearer $GREENFINCH_API_KEY"
Response · 200
{
  "success": true,
  "data": {
    "items": [
      {
        "event_id": "3f2a9c1e-0000-4000-8000-000000000301",
        "property_id": "3f2a9c1e-0000-4000-8000-000000000001",
        "event_type": "lead.qualified",
        "qualified_at": "2026-09-12T16:40:11.000Z",
        "created_at": "2026-09-12T16:40:11.204Z"
      }
    ],
    "next_cursor": "eyJjIjoiMjAyNi0wOS0xMlQxNjo0MDoxMS4yMDQxMjNaIiwiaSI6Ii4uLiJ9",
    "has_more": true
  }
}
  • Page with cursor until has_more is false, and store the last cursor to resume the next poll. The cursor is ordered by created_at, so no event is skipped or repeated.
  • An event appears in the feed about ten seconds after it is recorded.
  • Events for properties outside your service area, or no longer listed, are left out of the feed.
StatusCodeWhen
400—updated_since, cursor or limit is malformed.

Get a lead bundle

GET/leads/{id}scopeleads:read_single

The full CRM-ready record for one property — property facts, owner and manager firms, contacts, pipeline, notes and tasks. Each successful call is a metered export: free within your monthly allowance, then 1 credit.

Path parameters

NameTypeDescription
idrequireduuidThe Greenfinch property ID.
curl "https://app.greenfinch.ai/api/v1/leads/3f2a9c1e-0000-4000-8000-000000000001" \
  -H "Authorization: Bearer $GREENFINCH_API_KEY"
Response · 200 (abbreviated)
{
  "success": true,
  "data": {
    "bundle": {
      "bundle_version": 1,
      "greenfinch_property_id": "3f2a9c1e-0000-4000-8000-000000000001",
      "generated_at": "2026-09-14T15:04:05.000Z",
      "delivered_to_org": "org_example000000000001",
      "partial": false,
      "partial_reason": null,
      "property": {
        "address": "1200 Commerce Pkwy",
        "city": "Plano",
        "state": "TX",
        "…": "…"
      },
      "organizations": [
        "…"
      ],
      "contacts": [
        "…"
      ],
      "contacts_summary": null,
      "pipeline": {
        "in_pipeline": true,
        "status": "qualified",
        "…": "…"
      },
      "notes": [],
      "notes_digest": "",
      "actions": [],
      "actions_digest": ""
    }
  },
  "meta": {
    "billed_via": "quota",
    "credits_charged": 0
  }
}
  • meta.billed_via is quota (free) or credit; meta.credits_charged is what this call cost.
  • The Greenfinch-Delivery-Id response header identifies this delivery. It changes on every call — de-duplicate records on greenfinch_property_id instead.
  • Every call is a new export and is billed again. Store the bundle rather than re-fetching it.
  • The full field list is on Lead bundles.
StatusCodeWhen
400—id is not a UUID.
402INSUFFICIENT_CREDITSThe free allowance is used up and the credit balance cannot cover 1 credit (top-level code; also CREDITS_DORMANT or USER_CREDIT_LIMIT_EXCEEDED).
403OUT_OF_TERRITORYThe property is outside your organization's service area.
403NOT_ENTITLEDThe property is not unlocked or covered by your plan. Unlock it first.
404—No such property.
429DAILY_CAP_EXCEEDEDYour organization's daily lead-export limit is reached (CREDENTIAL_DAILY_CAP_EXCEEDED for this key's own limit). Retry after the time in meta.resetsAt.
503COMPLIANCE_CHECK_UNAVAILABLEA safety check could not run. Retry after Retry-After.

Write back a deal outcome

POST/leads/{id}/statusscopepipeline:write

Tells Greenfinch that a lead it handed to your CRM was won or lost, and optionally whether the property is now an active or churned customer. Available once your organization has set its CRM mode to external (your CRM owns deal stages).

Path parameters

NameTypeDescription
idrequireduuidThe Greenfinch property ID.

Body

NameTypeDescription
statusrequired"won" | "lost"The deal outcome.
customer_statusoptional"active" | "churned"Also mark the property's customer account. Won does not mark a customer by itself — send active explicitly.
curl -X POST "https://app.greenfinch.ai/api/v1/leads/3f2a9c1e-0000-4000-8000-000000000001/status" \
  -H "Authorization: Bearer $GREENFINCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "status": "won",
  "customer_status": "active"
}'
Response · 200
{
  "success": true,
  "data": {
    "property_id": "3f2a9c1e-0000-4000-8000-000000000001",
    "status": "won",
    "is_current_customer": true,
    "status_changed_at": "2026-09-14T15:10:42.000Z",
    "customer_account_status": "active"
  }
}

Safe to retry: sending the same status again changes nothing and returns the current state. No other body fields are accepted.

StatusCodeWhen
400—id is not a UUID, or the body is not JSON.
404—No such property.
404NO_PIPELINE_RECORDThe property was never in your pipeline. Write-back only updates leads Greenfinch handed off.
409CRM_MODE_NOT_EXTERNALYour organization manages stages in Greenfinch, not in an external CRM.
409CHANGED_IN_FLIGHTThe lead changed during the request. Retry.
422—status or customer_status has an unsupported value, or the body has extra fields.

Events

List lead events

GET/eventsscopeevents:read

Your organization's recent lead events, newest first, including whether each was delivered to your webhooks.

Query parameters

NameTypeDescription
daysoptionalintegerLook-back window, 1–90. Default 30.
limitoptionalintegerRows to return. Default 50; values above 200 are reduced to 200.
eventTypeoptional"lead.qualified" | "lead.changed" | "signal.<kind>" | "signal.withdrawn"Only this event type. The change (signal.*) types work once change events are switched on for your organization.
curl "https://app.greenfinch.ai/api/v1/events?days=7&eventType=lead.qualified" \
  -H "Authorization: Bearer $GREENFINCH_API_KEY"
Response · 200
{
  "success": true,
  "data": {
    "events": [
      {
        "id": "3f2a9c1e-0000-4000-8000-000000000301",
        "type": "lead.qualified",
        "property_id": "3f2a9c1e-0000-4000-8000-000000000001",
        "subject_type": null,
        "subject_id": null,
        "withdraws_event_id": null,
        "status": "fanned_out",
        "skip_reason": null,
        "by_agent": false,
        "occurred_at": "2026-09-12T16:40:11.000Z",
        "fanned_out_at": "2026-09-12T16:40:26.000Z",
        "test": false
      }
    ],
    "total": 1
  }
}
  • status is the delivery state of the event, not the lead: pending (not yet sent to webhooks), fanned_out (sent) or skipped (not sent, with skip_reason).
  • by_agent is true when an API key or AI assistant made the change. test marks events created as tests.
  • total counts every matching event in the window, not just this page.
  • A change event (signal.*) names its subject in subject_type and subject_id; property_id is empty for a change about a person, and a signal.withdrawn event names the event it retracts in withdraws_event_id.

Lists

List saved lists

GET/listsscopelists:read

Your organization's saved lists, newest first.

Query parameters

NameTypeDescription
typeoptional"properties" | "contacts"Only lists of this type.
limitoptionalintegerPage size. Default 100; values above 200 are reduced to 200.
offsetoptionalintegerRows to skip. Pass the previous page's next_cursor here.
curl "https://app.greenfinch.ai/api/v1/lists?type=properties" \
  -H "Authorization: Bearer $GREENFINCH_API_KEY"
Response · 200
{
  "success": true,
  "data": {
    "lists": [
      {
        "id": "3f2a9c1e-0000-4000-8000-000000000071",
        "list_name": "Plano office prospects",
        "list_type": "properties",
        "visibility": "team",
        "owner_name": "Jordan Example",
        "item_count": 42,
        "created_at": "2026-09-02T13:05:00.000Z"
      }
    ],
    "total": 1,
    "has_more": false,
    "next_cursor": null
  }
}

An organization key sees every list in the organization, including members' private lists.

Create a list

POST/listsscopelists:write

Body

NameTypeDescription
list_namerequiredstring1–200 characters.
list_typerequired"properties" | "contacts"What the list holds.
reuse_existing_by_nameoptionalbooleanWhen true, return an existing list of the same type and name (case-insensitive) instead of creating a duplicate. Default false.
curl -X POST "https://app.greenfinch.ai/api/v1/lists" \
  -H "Authorization: Bearer $GREENFINCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "list_name": "Plano office prospects",
  "list_type": "properties",
  "reuse_existing_by_name": true
}'
Response · 201 created, or 200 when reused
{
  "success": true,
  "data": {
    "list": {
      "id": "3f2a9c1e-0000-4000-8000-000000000071",
      "list_name": "Plano office prospects",
      "list_type": "properties",
      "visibility": "team",
      "item_count": 0
    },
    "created": true
  }
}
StatusCodeWhen
400—The body is not valid JSON or a field is invalid.
403FEATURE_GATEDYour plan does not include lists.

Add items to a list

POST/lists/{id}/itemsscopelists:write

Adds properties (to a properties list) or contacts (to a contacts list). All-or-nothing: if any item is unavailable, nothing is added.

Body

NameTypeDescription
item_idsrequireduuid[]1–500 property or contact IDs, matching the list's type.
curl -X POST "https://app.greenfinch.ai/api/v1/lists/3f2a9c1e-0000-4000-8000-000000000071/items" \
  -H "Authorization: Bearer $GREENFINCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "item_ids": [
    "3f2a9c1e-0000-4000-8000-000000000001",
    "3f2a9c1e-0000-4000-8000-000000000002"
  ]
}'
Response · 200
{
  "success": true,
  "data": {
    "list_id": "3f2a9c1e-0000-4000-8000-000000000071",
    "added": 1,
    "already_exists": 1
  }
}

Safe to retry: items already on the list are counted in already_exists.

StatusCodeWhen
400—id or an item ID is not a UUID, or there are more than 500 items.
403FEATURE_GATEDYour plan does not include lists.
404NOT_FOUNDNo such list.
404PROPERTY_NOT_AVAILABLEA property does not exist or is outside your service area.
404CONTACT_NOT_AVAILABLEA contact is not available to your organization.

Notes

List property notes

GET/notesscopenotes:read

Notes your team left on one property, newest first.

Query parameters

NameTypeDescription
propertyIdrequireduuidThe Greenfinch property ID.
limitoptionalinteger1–100. Default 50.
curl "https://app.greenfinch.ai/api/v1/notes?propertyId=3f2a9c1e-0000-4000-8000-000000000001" \
  -H "Authorization: Bearer $GREENFINCH_API_KEY"
Response · 200
{
  "success": true,
  "data": {
    "notes": [
      {
        "id": "3f2a9c1e-0000-4000-8000-000000000041",
        "property_id": "3f2a9c1e-0000-4000-8000-000000000001",
        "content": "Spoke with facilities; contract renews in Q1.",
        "author": "Jordan Example",
        "created_at": "2026-09-11T14:00:00.000Z"
      }
    ]
  }
}
StatusCodeWhen
400—propertyId is missing or not a UUID.
403FEATURE_GATEDYour plan does not include the pipeline, which notes belong to.
403OUT_OF_TERRITORYThe property is outside your service area.
404NOT_FOUNDNo such property.
409PROPERTY_RETIREDThe property was merged into another; meta.successorPropertyId names it.

Properties

Get a property

GET/properties/{id}scopeproperty:read

One property's record and research state. It never includes contact details — use a lead bundle or the MCP tools for those.

Path parameters

NameTypeDescription
idrequiredstringThe Greenfinch property ID, or its property key.
curl "https://app.greenfinch.ai/api/v1/properties/3f2a9c1e-0000-4000-8000-000000000001" \
  -H "Authorization: Bearer $GREENFINCH_API_KEY"
Response · 200
{
  "success": true,
  "data": {
    "property": {
      "id": "3f2a9c1e-0000-4000-8000-000000000001",
      "address": "1200 Commerce Pkwy",
      "city": "Plano",
      "state": "TX",
      "zip": "75074",
      "county": "Collin",
      "lat": 33.0198,
      "lon": -96.6989,
      "lotSqft": 217800,
      "buildingSqft": 96000,
      "yearBuilt": 2004,
      "yearBuiltDetail": {
        "basis": "per_building",
        "oldest": 1998,
        "newest": 2004,
        "primaryBuildingBasis": "area",
        "buildings": 3,
        "buildingsTotal": 3
      },
      "numFloors": 4,
      "numFloorsDetail": {
        "basis": "per_building",
        "max": 4,
        "areaWeightedMean": 3.51,
        "primaryBuilding": 4,
        "primaryBuildingBasis": "area",
        "buildings": 3,
        "buildingsTotal": 3
      },
      "effectiveYearBuilt": 2015,
      "effectiveYearBuiltDetail": {
        "basis": "per_building",
        "oldest": 2009,
        "newest": 2015,
        "primaryBuildingBasis": "area",
        "buildings": 2,
        "buildingsTotal": 3
      },
      "heatingType": "Rooftop Package Unit",
      "heatingTypeDetail": {
        "basis": "per_building",
        "buildings": 3,
        "buildingsTotal": 3,
        "areaShare": 1,
        "values": [
          {
            "value": "Rooftop Package Unit",
            "buildings": 2,
            "areaShare": 0.844
          },
          {
            "value": "Heat Pump",
            "buildings": 1,
            "areaShare": 0.156
          }
        ]
      },
      "coolingType": "Rooftop Package Unit",
      "coolingTypeDetail": {
        "basis": "per_building",
        "buildings": 3,
        "buildingsTotal": 3,
        "areaShare": 1,
        "values": [
          {
            "value": "Rooftop Package Unit",
            "buildings": 2,
            "areaShare": 0.844
          },
          {
            "value": "Heat Pump",
            "buildings": 1,
            "areaShare": 0.156
          }
        ]
      },
      "primaryConstructionType": "Concrete",
      "primaryConstructionTypeDetail": {
        "basis": "per_building",
        "buildings": 3,
        "buildingsTotal": 3,
        "areaShare": 1,
        "values": [
          {
            "value": "Concrete",
            "buildings": 3,
            "areaShare": 1
          }
        ]
      },
      "exteriorWallMaterial": null,
      "exteriorWallMaterialDetail": null,
      "foundationType": "Slab on Grade",
      "foundationTypeDetail": {
        "basis": "per_building",
        "buildings": 3,
        "buildingsTotal": 3,
        "areaShare": 1,
        "values": [
          {
            "value": "Slab on Grade",
            "buildings": 3,
            "areaShare": 1
          }
        ]
      },
      "roofCover": null,
      "roofCoverDetail": null,
      "roofShape": null,
      "roofShapeDetail": null,
      "qualityGrade": "Good",
      "qualityGradeDetail": {
        "basis": "per_building",
        "buildings": 3,
        "buildingsTotal": 3,
        "areaShare": 1,
        "values": [
          {
            "value": "Good",
            "buildings": 2,
            "areaShare": 0.844
          },
          {
            "value": "Average",
            "buildings": 1,
            "areaShare": 0.156
          }
        ]
      },
      "hasPool": false,
      "hasPoolDetail": {
        "basis": "per_building",
        "count": 0,
        "buildings": 3,
        "buildingsTotal": 3
      },
      "hasSprinklers": true,
      "hasSprinklersDetail": {
        "basis": "per_building",
        "count": 2,
        "buildings": 2,
        "buildingsTotal": 3
      },
      "buildingAttributesBasis": "per_building",
      "footprintArea": 21500,
      "footprintAreaSource": "measured",
      "assetCategory": "Office",
      "assetSubcategory": "Office Building",
      "commonName": "Northgate Business Park",
      "nameLocked": false,
      "recordOwner": "NORTHGATE COMMERCE LP",
      "beneficialOwner": "Northgate Holdings Group",
      "beneficialOwnerType": "private_investor",
      "managementCompany": "Lakeside Property Services",
      "aiRationale": "Four-story multi-tenant office building on a landscaped campus…",
      "enrichmentStatus": "completed",
      "lastEnrichedAt": "2026-08-20T14:11:00.000Z",
      "researchedAt": "2026-08-20T14:11:00.000Z",
      "researchTier": {
        "displayName": "Base research model",
        "description": "Our standard AI research pass.",
        "isUpgradeAvailable": true
      },
      "totalParval": 18450000,
      "premiumDataRedacted": false
    },
    "researchState": "revealed",
    "researchRevealed": true,
    "revealSource": "paid",
    "coveredByPlan": true
  }
}
  • researchState: revealed (you can read the research), locked (research exists; unlock the property to read it — ownership, management and the write-up are null until then) or none (not researched yet).
  • revealSource is paid when your organization unlocked the property, geo when your subscription covers its county, or null.
  • recordOwner is the owner of record at the county — the name on the deed, which is often an entity rather than the operator you want to reach. beneficialOwner and managementCompany are the researched answers.
  • totalParval (assessed value) and commonName are null on plans without those data, with premiumDataRedacted or nameLocked set to true.
  • Every property is addressed by its id, a UUID. That is the only property identifier the API returns, and the one to store if you are keeping a reference in your own system.
  • On a contact, employerMismatch is true when the employer we hold for that person disagrees with the firm they are attached to — a hint that they may have moved on. source describes the KIND of source a value came from (manual, data_provider, verified_employer_domain, enrichment, web, public_record), never an individual supplier.
StatusCodeWhen
400—id is empty.
403OUT_OF_TERRITORYThe property exists but is outside your service area; meta.reason is territory or subscription.
404NOT_FOUNDNo such property.
409PROPERTY_RETIREDThe property was merged into another; meta.successorPropertyId names it.

Customer accounts

Sync customer accounts

POST/customer-accounts/syncscopeaccounts:sync

Tells Greenfinch which properties are your active or churned customers, so reps see them flagged. Each row is processed on its own: one bad row does not fail the batch.

Body

NameTypeDescription
accountsrequiredobject[]1–500 rows. Split larger syncs across requests.
accounts[].property_idrequiredstringThe Greenfinch property ID.
accounts[].statusrequired"active" | "churned"The customer's status.
curl -X POST "https://app.greenfinch.ai/api/v1/customer-accounts/sync" \
  -H "Authorization: Bearer $GREENFINCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "accounts": [
    {
      "property_id": "3f2a9c1e-0000-4000-8000-000000000001",
      "status": "active"
    },
    {
      "property_id": "3f2a9c1e-0000-4000-8000-000000000009",
      "status": "churned"
    }
  ]
}'
Response · 200
{
  "success": true,
  "data": {
    "results": [
      {
        "property_id": "3f2a9c1e-0000-4000-8000-000000000001",
        "status": "synced"
      },
      {
        "property_id": "3f2a9c1e-0000-4000-8000-000000000009",
        "status": "synced",
        "out_of_service_area": true
      }
    ],
    "synced_count": 2,
    "error_count": 0,
    "out_of_service_area_count": 1
  }
}
  • A row that could not be synced has status: "error" and an error message.
  • out_of_service_area: true means the row was saved but the property is outside the area your organization has switched on, so reps will not see it on the map until it is.
  • Churn never deletes the account; syncing active again restores it.
StatusCodeWhen
400—The body is invalid, or has more than 500 rows.

Webhooks

Register an HTTPS endpoint to receive a signed lead.qualified delivery each time a lead is qualified. Delivery format and signature verification are on Webhooks.

Subscribe a webhook

POST/webhooksscopeevents:subscribe

Body

NameTypeDescription
urlrequiredstringA public https:// URL. Local, private and credential-bearing URLs are refused.
eventsoptionalstring[]Event types to receive. Default: "lead.qualified" only. The change event types ("signal.ownership_transfer", "signal.sale", "signal.manager_change", "signal.contact_departure", "signal.contact_title_change", "signal.contact_employer_change", "signal.withdrawn") are named explicitly, once change events are switched on for your organization; see Webhooks.
include_evidenceoptionalbooleanChange events only: include each change's evidence (values before and after) in the body. Default false: the body names the change and you read the evidence with greenfinch_get_changes.
curl -X POST "https://app.greenfinch.ai/api/v1/webhooks" \
  -H "Authorization: Bearer $GREENFINCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://crm.example.com/hooks/greenfinch",
  "events": [
    "lead.qualified"
  ]
}'
Response · 201
{
  "success": true,
  "data": {
    "endpoint": {
      "id": "3f2a9c1e-0000-4000-8000-000000000501",
      "url": "https://crm.example.com/hooks/greenfinch",
      "label": null,
      "subscribedEvents": [
        "lead.qualified"
      ],
      "active": true,
      "createdVia": "api",
      "createdAt": "2026-09-14T15:20:00.000Z"
    },
    "signing_secret": "whsec_…",
    "superseded_endpoint_id": null
  }
}
  • signing_secret is returned once. Store it; you need it to verify every delivery.
  • Subscribing the same URL again replaces the old subscription (its ID comes back in superseded_endpoint_id) with a new secret, so a retried request never creates duplicate deliveries.
  • Endpoints created through the API report createdVia: "api"; endpoints added in the app report manual. Endpoints registered before 14 September 2026 report zapier, which is what this endpoint recorded for every API caller at the time.
StatusCodeWhen
400—The URL is not a public https:// URL, or events is empty or unknown.
409WEBHOOK_ENDPOINT_LIMIT_REACHEDYour organization already has 10 active endpoints.
409WEBHOOK_ENDPOINT_TOTAL_LIMIT_REACHEDYour organization has 100 endpoints including deactivated ones.

Unsubscribe a webhook

DELETE/webhooks/{id}scopeevents:subscribe

Stops deliveries to an endpoint. Repeating the call returns the same answer.

curl -X DELETE "https://app.greenfinch.ai/api/v1/webhooks/3f2a9c1e-0000-4000-8000-000000000501" \
  -H "Authorization: Bearer $GREENFINCH_API_KEY"
Response · 200
{
  "success": true,
  "data": {
    "id": "3f2a9c1e-0000-4000-8000-000000000501",
    "active": false
  }
}
StatusCodeWhen
404—No such endpoint in your organization.