Partner Prospect API
The Prospect API lets you push renters to Greenlight directly. You send what you know, Greenlight validates it synchronously and checks eligibility in the background, and once the prospect is eligible it exposes an enrollment URL you send the renter to.
Greenlight's preferred ingestion path is Partner Webhook Events, where you emit your existing events and write no client at all. Reach for the API when you want to go live without waiting on Greenlight to build an adapter for your schema, when you want data problems reported back as a 422 at the moment you send them, or when you want to read prospect state on demand. Reading is useful on any integration — the GET endpoint works even if you write through events.
The write endpoint is an upsert keyed on (source, source_prospect_id) — the same key events use, so the two paths address the same records and can be mixed.
See Authentication & Environments for base URLs and request signing.
Create or update a prospect
POST /partners/{owner_slug}/prospects
Content-Type: application/json
{
"prospect": {
"source": "acme",
"source_prospect_id": "your-prospect-123",
"email": "renter@example.com",
"phone": "5559876543",
"first_name": "Jordan",
"last_name": "Rivera",
"birthdate": "1990-04-12",
"lease_start_date": "2026-09-01",
"lease_end_date": "2027-08-31",
"monthly_rent_cents": 200000,
"deposit_amount_cents": 200000,
"guarantor_coverage": false,
"screening_result": "approved",
"employment_status": "employed",
"yearly_income": 95000,
"has_ssn": true,
"social_security_number": "123-45-6789",
"pms_type": "yardi",
"pms_property_id": "prop-1",
"pms_unit_id": "unit-101",
"pms_prospect_id": "p0001234",
"terms_accepted_at": "2026-07-24T15:04:05Z"
}
}
Both a create and an update return the full prospect object with an HTTP status of 200. The status_code field inside the body is 201 when the prospect was created and 200 when an existing one was updated, so read that rather than the HTTP status if you need to tell them apart.
Only fields you include are updated. Omitting a field leaves the stored value untouched, so you can send partial updates as data becomes available.
Fetch a prospect
GET /partners/{owner_slug}/prospects/{source}/{source_prospect_id}
Returns the same prospect object. Use this to poll for eligibility if you are not subscribed to prospect.ready webhooks. After enrollment, offered_products is useful for security deposit insurance and cash deposit; for Greenlight Guarantee, read latest_coverage after purchase — offered_products stays empty.
Request fields
Three fields are enforced at write time. A request missing any of them is rejected with 422:
| Field | Notes |
|---|---|
source | Your partner code, assigned by Greenlight. Must match a configured source. |
source_prospect_id | Your identifier for the renter. Unique per property owner and source. |
terms_accepted_at | ISO 8601 timestamp of when the renter accepted Greenlight's terms in your flow |
Everything else is accepted as null, but the prospect stays incomplete and cannot enroll until the fields listed in Prospect Data Requirements are present.
All monetary values are integer cents (200000 is $2,000.00). Dates are YYYY-MM-DD; timestamps are ISO 8601.
Selecting Greenlight Guarantee
guarantor_coverage is an optional boolean and defaults to false. Send true when the renter should be offered Greenlight Guarantee instead of a deposit product. Greenlight then evaluates Greenlight Guarantee eligibility for the property and marks the prospect ineligible for both security deposit insurance and cash deposit — the products are mutually exclusive. Once eligibility is written, that exclusivity shows up as a reason on those two products rather than as an error on your request.
Nothing else about Greenlight Guarantee is writable. Greenlight resolves the guarantor application rule and the Greenlight Guarantee product version from the property's configuration; do not send an identifier or a version for either. deposit_amount_cents is not required for a guarantor_coverage: true prospect to leave incomplete — it's never the Greenlight Guarantee coverage amount, so omit it if the renter isn't giving a cash deposit.
Identifying the building and unit
A prospect must resolve to a building and a unit before it can enroll. How you identify them depends on how the property owner is configured:
- Owners synced from a PMS identify the building and unit with the
pms_type,pms_property_id, andpms_unit_idfields shown in the request above. Greenlight matches them against the resident data it syncs from the PMS. - Owners you provision through the Partner Onboarding API identify them with nested
propertyandunitobjects, keyed on your ownexternal_system_id, the same identifiers you loaded through the properties and units endpoints. On these owners thepms_*fields are not used; send the objects instead.
{
"prospect": {
"source": "acme",
"source_prospect_id": "your-prospect-123",
"//": "renter and lease fields as above",
"property": { "external_system_id": "acme-prop-123" },
"unit": { "external_system_id": "acme-unit-456" }
}
}
Each object resolves in one of three ways:
- An identifier Greenlight recognizes. The prospect attaches to that building or unit as a reference. Any other fields in the object are ignored, because roster changes go through the properties and units endpoints, never through a prospect.
- An unrecognized identifier with a full item. The building or unit is created, following the property or unit rules. If creation fails, the request returns
422with that item'serrorsand the prospect is not created. propertyabsent. The prospect is stored but cannot enroll, and no enrollment URL is returned.
An enrollment URL requires both the property and the unit to resolve. A prospect with only a property reference is stored but cannot enroll until its unit exists.
For an owner provisioned through the Onboarding API, deposit_amount_cents on the prospect is the renter's coverage amount, and property level coverage rules apply only where no deposit amount is supplied.
Response
{
"status_code": 201,
"prospect": {
"id": 4821,
"source": "acme",
"source_prospect_id": "your-prospect-123",
"email": "renter@example.com",
"phone": "5559876543",
"first_name": "Jordan",
"last_name": "Rivera",
"birthdate": "1990-04-12",
"lease_start_date": "2026-09-01",
"lease_end_date": "2027-08-31",
"screening_result": "approved",
"monthly_rent_cents": 200000,
"deposit_amount_cents": 200000,
"effective_coverage_amount_cents": 200000,
"guarantor_coverage": false,
"employment_status": "employed",
"yearly_income": 95000,
"has_ssn": true,
"pms_type": "yardi",
"pms_property_id": "prop-1",
"pms_unit_id": "unit-101",
"pms_prospect_id": "p0001234",
"status": "ready",
"percent_savings": 93.75,
"enrollment_url": "https://www.greenlightrent.com/enrollment/embedded/acme-properties/acme/your-prospect-123?email=renter%40example.com",
"hosted_enrollment_url": "https://www.greenlightrent.com/enrollment/expedited/acme-properties/acme/your-prospect-123?email=renter%40example.com",
"offered_products": [
{
"product_type": "security_deposit_insurance",
"upfront_premium_cents": 12500,
"monthly_premium_cents": 1400,
"upfront_enrollment_url": "https://www.greenlightrent.com/enrollment/embedded/...&selected_product=security_deposit_insurance&cadence=upfront",
"monthly_enrollment_url": "https://www.greenlightrent.com/enrollment/embedded/...&selected_product=security_deposit_insurance&cadence=monthly"
},
{
"product_type": "cash_deposit",
"cash_deposit_amount_cents": 200000,
"enrollment_url": "https://www.greenlightrent.com/enrollment/embedded/...&selected_product=cash_deposit"
}
],
"application_submitted": false,
"latest_coverage": null
}
}
Every successful response is wrapped with a status_code key mirroring the HTTP status.
Note that id here is Greenlight's numeric database ID. Webhook payloads use a prefixed string ID (tpp_…) for the same record. The stable identifier across both is your own source_prospect_id.
social_security_number, education_level, citizenship, and terms_accepted_at are accepted on write but are not echoed back in the response.
Greenlight Guarantee response
A prospect sent with guarantor_coverage: true carries its own eligibility fields:
{
"guarantor_coverage": true,
"rg_eligibility_status": "eligible",
"rg_ineligibility_reasons": [],
"status": "ready",
"enrollment_url": "https://www.greenlightrent.com/enrollment/embedded/acme-properties/acme/your-prospect-123?email=renter%40example.com",
"offered_products": [],
"sdi_ineligibility_reasons": ["Renter Guarantee (guarantor coverage) selected — not eligible for SDA or cash deposit"],
"cd_ineligibility_reasons": ["Renter Guarantee (guarantor coverage) selected — not eligible for SDA or cash deposit"]
}
Three things differ from the deposit-product response. rg_eligibility_status is omitted while eligibility has not been decided — the prospect is incomplete, or processing after a write cleared the previous decision — so read a missing key as "not decided yet", not as ineligible. rg_ineligibility_reasons is always present while guarantor_coverage is true, as [] when there is nothing to report. And offered_products never contains a priced Greenlight Guarantee row: it holds security deposit insurance and cash deposit only, so it stays empty for the whole Greenlight Guarantee lifecycle.
That means there is nothing to wait for beyond enrollment_url and hosted_enrollment_url. Once they are populated, send the renter to one of them; pricing is presented inside the enrollment flow.
The Greenlight Guarantee coverage amount comes from the property's guarantor application rule, or the property or unit coverage settings, and may be a rent multiplier or a fixed amount — not from deposit_amount_cents or effective_coverage_amount_cents. There is no top-level Greenlight Guarantee coverage field; after purchase it appears as coverage_amount_cents on latest_coverage.
Response fields
| Field | Description |
|---|---|
status | incomplete, processing, ready, or error |
effective_coverage_amount_cents | The deposit figure Greenlight's deposit products are written against, derived from the property owner's rules rather than your deposit_amount_cents. Not the Greenlight Guarantee coverage amount. |
percent_savings | Savings on a security deposit insurance premium versus the deposit it replaces, as a percentage. Empty until the renter has been underwritten in the enrollment flow, and always empty on a Greenlight Guarantee prospect. |
enrollment_url | Link to start enrollment, on the flow your source is configured for. null unless the prospect is eligible for at least one product. |
hosted_enrollment_url | The same enrollment, always on the expedited (full-page) flow, for a renter you cannot iframe — a native app, or a link in an email or SMS. Same nil semantics as enrollment_url, and identical to it when your source is already expedited. Load it top-level: it is not frameable. |
offered_products | Priced deposit products with prefilled enrollment links, on your source's configured flow — there is no hosted variant. Empty until the renter has been underwritten in the enrollment flow, and always empty on a Greenlight Guarantee prospect. |
application_submitted | Whether the renter has submitted an application |
latest_coverage | The renter's existing policy or cash deposit for this unit, if any. type is security_deposit_insurance, renter_guarantee_insurance, jetty_deposit, or cash_deposit. |
guarantor_coverage | Whether the prospect is on Greenlight Guarantee. Always present. |
rg_eligibility_status | eligible or ineligible. Present only when guarantor_coverage is true, and omitted then too until eligibility has been decided. |
rg_ineligibility_reasons | Human-readable reasons Greenlight Guarantee was declined. Present whenever guarantor_coverage is true, as an empty array when there are none; omitted entirely when it is false. |
sdi_ineligibility_reasons | Human-readable reasons Greenlight Deposit was declined. Omitted when there are none. After eligibility is written on a Greenlight Guarantee prospect, this carries the product-exclusivity reason. |
cd_ineligibility_reasons | Human-readable reasons cash deposit was declined. Omitted when there are none. After eligibility is written on a Greenlight Guarantee prospect, this carries the product-exclusivity reason. |
Product objects
offered_products contains up to two entries, both deposit products:
security_deposit_insurance — upfront_premium_cents, monthly_premium_cents, upfront_enrollment_url, monthly_enrollment_url
cash_deposit — cash_deposit_amount_cents, enrollment_url
There is no renter_guarantee_insurance entry. Greenlight Guarantee is priced inside the enrollment flow, so do not gate sending a Greenlight Guarantee renter on a product row appearing here.
Prospect lifecycle
incomplete ──(all required fields present)──> processing ──(eligibility)──> ready
▲ │
└──────────────(a required field is cleared)──────────────────────────────┘
incomplete— required fields are missing. No enrollment URL.processing— Greenlight has what it needs and is checking eligibility. This is asynchronous and typically takes seconds.ready— Greenlight has finished processing. The enrollment URLs are populated if the prospect is eligible for at least one product that applies to it; send the renter to one of them.error— eligibility could not be determined. Contact your Partner Success Manager.
A prospect that is ready but ineligible for a given product carries the reasons in sdi_ineligibility_reasons, cd_ineligibility_reasons, or rg_ineligibility_reasons. When every applicable product is declined, both enrollment URLs are null, so gate on the URL rather than on status.
Read those reasons per product rather than in aggregate. After eligibility is written, a Greenlight Guarantee prospect is by design ineligible for security deposit insurance and cash deposit, and carries the exclusivity reason on both, while still having populated enrollment URLs when Greenlight Guarantee itself is eligible.
If enrollment_url is null on a Greenlight Guarantee prospect that is ready with rg_eligibility_status of ineligible, rg_ineligibility_reasons explains why. During incomplete or processing, a null URL with no rg_eligibility_status and an empty rg_ineligibility_reasons array means eligibility has not been decided yet.
Because processing resolves asynchronously, subscribe to the prospect.ready webhook rather than polling in a tight loop.
ready does not mean the prospect is priced. Underwriting needs a few data points Greenlight collects from the renter during enrollment, so offered_products and percent_savings stay empty until the renter has been through the flow — and on a Greenlight Guarantee prospect they never populate. Do not build a UI that quotes Greenlight before then.
Errors
Validation failures return 422 with per-field messages keyed by attribute name:
{
"status_code": 422,
"error": "Prospect invalid.",
"prospect": {
"source_prospect_id": "Source prospect has already been taken",
"terms_accepted_at": "Terms accepted at can't be blank"
}
}
| Status | Meaning |
|---|---|
401 | Request signature missing, malformed, or older than five minutes |
404 | Unknown owner_slug, or the prospect does not exist on GET |
422 | Validation failure — see the prospect object for field-level detail |
Test data reset
In staging only, you can clear test records so you can re-run an enrollment with the same identifiers:
DELETE /residents/reset?email={email}&owner_slug={owner_slug}
Both parameters are required, and both go in the query string rather than the body. This clears the renter's Greenlight user along with the prospects and policies attached to it, and returns every resident matching the email under that owner to a pre-enrollment state — unlinking the policy, removing the tenant record, and re-evaluating eligibility so an earlier decline does not carry over. The same email can then go through enrollment again. It returns 404 in production.
The call is idempotent: an email with nothing left to clear returns 200 with empty id lists rather than an error, so it is safe to call unconditionally at the start of every test run. What it cannot do is create a resident — if the email has no resident under the owner, there is nothing to enroll and you will need one set up for you.
Note the path: unlike the rest of the API, this one is not scoped under /partners/{owner_slug}.
Next steps
- Prospect Data Requirements — which fields drive completeness
- Embedded purchase flow — using the returned enrollment URL
- Webhooks Overview — reacting to status changes