Reference · REST
REST API
Base URL
https://app.greenfinch.ai/api/v1All 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": true,
"data": {
"…": "endpoint-specific"
},
"meta": {
"…": "optional"
}
}{
"success": false,
"error": "Human-readable message",
"meta": {
"code": "MACHINE_CODE",
"…": "optional detail"
}
}- Check the HTTP status first, then
success.datais 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 ascodeinstead. Readmeta?.code ?? code. - Validation errors (
400,422) usually have onlyerror. 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
400rather than ignored, so a typo fails loudly. - A
400for bad arguments lists each problem inmeta.issuesas{ field, message }. - Refusals become HTTP statuses:
402out of credits or a failed subscription payment,403outside your territory or plan,404not found,409retired, not yet researched, or the same unlock already in progress (withRetry-After: 1),429a daily limit (withRetry-After). The code is inmeta.code. - These endpoints accept organization API keys only. A person's signed-in connection uses MCP instead (
ACTING_USER_NOT_SUPPORTED).
{
"success": false,
"error": "Invalid request — Unrecognized key(s) in object: 'adress'",
"meta": {
"issues": [
{
"field": null,
"message": "Unrecognized key(s) in object: 'adress'"
}
]
}
}Search properties
/properties/searchscopedata:readFind 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
| Name | Type | Description |
|---|---|---|
queryoptional | string | Free-text search across address, city, owner, name. |
boundsoptional | object | — |
filtersoptional | object | — |
limitoptional | integer | Max results, default 50, max 200. |
cursoroptional | string | Pagination cursor from a prior `query`-mode call. |
offsetoptional | integer | Rows to skip in a county/bounds search — page 2 of a ranked list is offset=limit. |
sortByoptional | string (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
}'{
"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"
]
}
}
}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 backnext_cursor). Location and map searches page withoffsetand also returntotal_matching,next_offsetand acategory_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
/properties/{id}/detailscopedata:readThe 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"{
"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
}
}researchStateisrevealed,locked(unlock to read ownership, management, the write-up and contacts;contactsSummarythen lists the title of each contact and which details exist) ornone(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— andphotoUrlwere 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.
| Status | Code | When |
|---|---|---|
403 | OUT_OF_TERRITORY | The property is outside your service area; meta.reason is territory or subscription. |
404 | NOT_FOUND | No such property. |
409 | PROPERTY_RETIRED | The property was merged; meta.successorPropertyId names the new ID. |
Search contacts by name or employer
/contacts/searchscopedata:readFind 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
| Name | Type | Description |
|---|---|---|
queryrequired | string | Name or employer fragment, min 2 chars. |
limitoptional | integer | Max 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"{
"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.
| Status | Code | When |
|---|---|---|
400 | — | A parameter is unknown, invalid, or given more than once (for example ?query=a&query=b); meta.issues names each field. |
Browse contacts
/contacts/browsescopedata:readList 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)
| Name | Type | Description |
|---|---|---|
roleTypeoptional | string | Contact role-type taxonomy key (see contact-role-taxonomy). |
titleTextoptional | string | Substring match on the contact's title. |
countyFipsoptional | string[] | 5-digit county FIPS codes, matched via the contact's attached properties. |
revealedStateoptional | "revealed" | "locked" | "any" | Default any. |
employerTextoptional | string | Substring match on the contact's employer name. |
organizationIdoptional | string | Organization UUID: only that firm's people (a current employment record at the firm, or, with marketCountyFips, anyone the market ranking ties to the firm). |
marketCountyFipsoptional | string | 5-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. |
limitoptional | integer | Max rows, default 25, max 200. |
offsetoptional | integer | Rows 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. |
changedWithinDaysoptional | integer | Only 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
}'{
"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
/contacts/{id}scopedata:readOne 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"{
"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
/contacts/{id}/relationshipsscopedata:readEvery 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"{
"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
/organizations/browsescopedata:readThe 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)
| Name | Type | Description |
|---|---|---|
roleoptional | string (one of 5) | One of: owner, property_manager, operator, tenant, any. |
countyFipsoptional | string[] | 5-digit county FIPS codes. |
queryoptional | string | Substring match on the firm's name. |
minPropertiesoptional | integer | Only firms with at least this many in-territory properties. |
sortoptional | "propertyCount" | "name" | Default name. |
limitoptional | integer | Max rows, default 25, max 200. |
offsetoptional | integer | Rows 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"
}'{
"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
/organizations/{id}scopedata:readOne 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"{
"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
/organizations/{id}/propertiesscopedata:readEvery 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"{
"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
/properties/{id}/unlockscopedata:unlockUnlocks 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"{
"success": true,
"data": {
"alreadyRevealed": false,
"creditsCharged": 10
}
}The response confirms the unlock only; read the contacts with GET /properties/{id}/detail.
| Status | Code | When |
|---|---|---|
402 | INSUFFICIENT_CREDITS | Not enough credits; meta.required and meta.available give the numbers. |
402 | PAYMENT_FAILED | Your subscription payment failed, so credits cannot be spent. Nothing was charged; an admin updates the payment method on the Billing page. |
403 | OUT_OF_TERRITORY | The property is outside your service area. |
404 | NOT_FOUND | No such property. |
409 | ACTION_IN_PROGRESS | Another request is already unlocking this property. Nothing was charged; retry after Retry-After (1 second). |
409 | NOT_RESEARCHED | The property has no research to unlock yet. |
409 | PROPERTY_RETIRED | The property was merged; use meta.successorPropertyId. |
Reveal a contact
/contacts/{id}/revealscopedata:unlockReveals 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"{
"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
}
}phoneLabelisMobile,Direct,Officeornull;phoneFallbackFromEmployer: truemeans 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.
| Status | Code | When |
|---|---|---|
402 | INSUFFICIENT_CREDITS | Not enough credits; meta.required and meta.available give the numbers. |
402 | PAYMENT_FAILED | Your subscription payment failed, so credits cannot be spent. Nothing was charged. |
404 | NOT_FOUND | No such contact, or it is not available to your organization. |
409 | ACTION_IN_PROGRESS | Another request is already revealing this contact. Nothing was charged; retry after Retry-After (1 second). |
429 | DAILY_CAP_EXCEEDED | The organization's daily contact-reveal limit is used (CREDENTIAL_DAILY_CAP_EXCEEDED for this key's). Wait for Retry-After. |
Account
Check a key
/mescopeany live keyConfirms 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"{
"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
| Name | Type | Description |
|---|---|---|
org_idrequired | string | Your organization's ID. |
credential_idrequired | string | This key's ID. |
scopesrequired | string[] | Scopes granted to the key. |
labelrequired | string | The key's name and its first 12 characters, for display. |
Get account status
/accountscopeaccount:readCredits, 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"{
"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
| Name | Type | Description |
|---|---|---|
credit_balancerequired | object | Spendable credits: total and its parts (included with the plan, purchased, trial, enterprise_pool). |
egress_quotarequired | object | The 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. |
entitlementrequired | object | The states and counties your subscription licenses (or nationwide). |
serviceable_arearequired | object | The area your organization has switched on — what keys can see. |
tierrequired | object | Plan 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
/leadsscopeevents:subscribeA 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
| Name | Type | Description |
|---|---|---|
updated_sinceoptional | ISO-8601 timestamp | Only events recorded at or after this time. Ignored when cursor is sent. |
cursoroptional | string | The next_cursor from the previous page. Opaque; do not build it yourself. |
limitoptional | integer | Page 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"{
"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
cursoruntilhas_moreisfalse, and store the last cursor to resume the next poll. The cursor is ordered bycreated_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.
| Status | Code | When |
|---|---|---|
400 | — | updated_since, cursor or limit is malformed. |
Get a lead bundle
/leads/{id}scopeleads:read_singleThe 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
| Name | Type | Description |
|---|---|---|
idrequired | uuid | The Greenfinch property ID. |
curl "https://app.greenfinch.ai/api/v1/leads/3f2a9c1e-0000-4000-8000-000000000001" \
-H "Authorization: Bearer $GREENFINCH_API_KEY"{
"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_viaisquota(free) orcredit;meta.credits_chargedis what this call cost.- The
Greenfinch-Delivery-Idresponse header identifies this delivery. It changes on every call — de-duplicate records ongreenfinch_property_idinstead. - 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.
| Status | Code | When |
|---|---|---|
400 | — | id is not a UUID. |
402 | INSUFFICIENT_CREDITS | The free allowance is used up and the credit balance cannot cover 1 credit (top-level code; also CREDITS_DORMANT or USER_CREDIT_LIMIT_EXCEEDED). |
403 | OUT_OF_TERRITORY | The property is outside your organization's service area. |
403 | NOT_ENTITLED | The property is not unlocked or covered by your plan. Unlock it first. |
404 | — | No such property. |
429 | DAILY_CAP_EXCEEDED | Your 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. |
503 | COMPLIANCE_CHECK_UNAVAILABLE | A safety check could not run. Retry after Retry-After. |
Write back a deal outcome
/leads/{id}/statusscopepipeline:writeTells 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
| Name | Type | Description |
|---|---|---|
idrequired | uuid | The Greenfinch property ID. |
Body
| Name | Type | Description |
|---|---|---|
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"
}'{
"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.
| Status | Code | When |
|---|---|---|
400 | — | id is not a UUID, or the body is not JSON. |
404 | — | No such property. |
404 | NO_PIPELINE_RECORD | The property was never in your pipeline. Write-back only updates leads Greenfinch handed off. |
409 | CRM_MODE_NOT_EXTERNAL | Your organization manages stages in Greenfinch, not in an external CRM. |
409 | CHANGED_IN_FLIGHT | The 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
/eventsscopeevents:readYour organization's recent lead events, newest first, including whether each was delivered to your webhooks.
Query parameters
| Name | Type | Description |
|---|---|---|
daysoptional | integer | Look-back window, 1–90. Default 30. |
limitoptional | integer | Rows 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"{
"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
}
}statusis the delivery state of the event, not the lead:pending(not yet sent to webhooks),fanned_out(sent) orskipped(not sent, withskip_reason).by_agentistruewhen an API key or AI assistant made the change.testmarks events created as tests.totalcounts every matching event in the window, not just this page.- A change event (
signal.*) names its subject insubject_typeandsubject_id;property_idis empty for a change about a person, and asignal.withdrawnevent names the event it retracts inwithdraws_event_id.
Lists
List saved lists
/listsscopelists:readYour organization's saved lists, newest first.
Query parameters
| Name | Type | Description |
|---|---|---|
typeoptional | "properties" | "contacts" | Only lists of this type. |
limitoptional | integer | Page size. Default 100; values above 200 are reduced to 200. |
offsetoptional | integer | Rows 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"{
"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
/listsscopelists:writeBody
| Name | Type | Description |
|---|---|---|
list_namerequired | string | 1–200 characters. |
list_typerequired | "properties" | "contacts" | What the list holds. |
reuse_existing_by_nameoptional | boolean | When 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
}'{
"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
}
}| Status | Code | When |
|---|---|---|
400 | — | The body is not valid JSON or a field is invalid. |
403 | FEATURE_GATED | Your plan does not include lists. |
Add items to a list
/lists/{id}/itemsscopelists:writeAdds properties (to a properties list) or contacts (to a contacts list). All-or-nothing: if any item is unavailable, nothing is added.
Body
| Name | Type | Description |
|---|---|---|
item_idsrequired | uuid[] | 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"
]
}'{
"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.
| Status | Code | When |
|---|---|---|
400 | — | id or an item ID is not a UUID, or there are more than 500 items. |
403 | FEATURE_GATED | Your plan does not include lists. |
404 | NOT_FOUND | No such list. |
404 | PROPERTY_NOT_AVAILABLE | A property does not exist or is outside your service area. |
404 | CONTACT_NOT_AVAILABLE | A contact is not available to your organization. |
Notes
List property notes
/notesscopenotes:readNotes your team left on one property, newest first.
Query parameters
| Name | Type | Description |
|---|---|---|
propertyIdrequired | uuid | The Greenfinch property ID. |
limitoptional | integer | 1–100. Default 50. |
curl "https://app.greenfinch.ai/api/v1/notes?propertyId=3f2a9c1e-0000-4000-8000-000000000001" \
-H "Authorization: Bearer $GREENFINCH_API_KEY"{
"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"
}
]
}
}| Status | Code | When |
|---|---|---|
400 | — | propertyId is missing or not a UUID. |
403 | FEATURE_GATED | Your plan does not include the pipeline, which notes belong to. |
403 | OUT_OF_TERRITORY | The property is outside your service area. |
404 | NOT_FOUND | No such property. |
409 | PROPERTY_RETIRED | The property was merged into another; meta.successorPropertyId names it. |
Properties
Get a property
/properties/{id}scopeproperty:readOne property's record and research state. It never includes contact details — use a lead bundle or the MCP tools for those.
Path parameters
| Name | Type | Description |
|---|---|---|
idrequired | string | The 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"{
"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 arenulluntil then) ornone(not researched yet).revealSourceispaidwhen your organization unlocked the property,geowhen your subscription covers its county, ornull.recordOwneris 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.beneficialOwnerandmanagementCompanyare the researched answers.totalParval(assessed value) andcommonNamearenullon plans without those data, withpremiumDataRedactedornameLockedset totrue.- 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,
employerMismatchistruewhen the employer we hold for that person disagrees with the firm they are attached to — a hint that they may have moved on.sourcedescribes the KIND of source a value came from (manual,data_provider,verified_employer_domain,enrichment,web,public_record), never an individual supplier.
| Status | Code | When |
|---|---|---|
400 | — | id is empty. |
403 | OUT_OF_TERRITORY | The property exists but is outside your service area; meta.reason is territory or subscription. |
404 | NOT_FOUND | No such property. |
409 | PROPERTY_RETIRED | The property was merged into another; meta.successorPropertyId names it. |
Customer accounts
Sync customer accounts
/customer-accounts/syncscopeaccounts:syncTells 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
| Name | Type | Description |
|---|---|---|
accountsrequired | object[] | 1–500 rows. Split larger syncs across requests. |
accounts[].property_idrequired | string | The 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"
}
]
}'{
"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 anerrormessage. out_of_service_area: truemeans 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.
| Status | Code | When |
|---|---|---|
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
/webhooksscopeevents:subscribeBody
| Name | Type | Description |
|---|---|---|
urlrequired | string | A public https:// URL. Local, private and credential-bearing URLs are refused. |
eventsoptional | string[] | 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_evidenceoptional | boolean | Change 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"
]
}'{
"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_secretis 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 reportmanual. Endpoints registered before 14 September 2026 reportzapier, which is what this endpoint recorded for every API caller at the time.
| Status | Code | When |
|---|---|---|
400 | — | The URL is not a public https:// URL, or events is empty or unknown. |
409 | WEBHOOK_ENDPOINT_LIMIT_REACHED | Your organization already has 10 active endpoints. |
409 | WEBHOOK_ENDPOINT_TOTAL_LIMIT_REACHED | Your organization has 100 endpoints including deactivated ones. |
Unsubscribe a webhook
/webhooks/{id}scopeevents:subscribeStops 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"{
"success": true,
"data": {
"id": "3f2a9c1e-0000-4000-8000-000000000501",
"active": false
}
}| Status | Code | When |
|---|---|---|
404 | — | No such endpoint in your organization. |