Skip to main content

Partner Prospect API

The Prospect API lets you push renters to Rhino directly. You send what you know, Rhino 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.

Rhino'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 Rhino 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,
"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"
}
}

Returns 201 when the prospect is created and 200 when an existing prospect is updated. Both return the full prospect object.

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, or to read offered_products and latest_coverage after the renter has been through the enrollment flow.

Request fields

Three fields are enforced at write time. A request missing any of them is rejected with 422:

FieldNotes
sourceYour partner code, assigned by Rhino. Must match a configured source.
source_prospect_idYour identifier for the renter. Unique per property owner and source.
terms_accepted_atISO 8601 timestamp of when the renter accepted Rhino'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.

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,
"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.sayrhino.com/enrollment/embedded/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.sayrhino.com/enrollment/embedded/...&selected_product=security_deposit_insurance&cadence=upfront",
"monthly_enrollment_url": "https://www.sayrhino.com/enrollment/embedded/...&selected_product=security_deposit_insurance&cadence=monthly"
},
{
"product_type": "cash_deposit",
"cash_deposit_amount_cents": 200000,
"enrollment_url": "https://www.sayrhino.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 Rhino'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.

Response fields

FieldDescription
statusincomplete, processing, ready, or error
effective_coverage_amount_centsCoverage Rhino will offer, derived from the property owner's rules rather than your deposit_amount_cents
percent_savingsSavings versus the cash deposit, as a percentage. Empty until the renter has been underwritten in the enrollment flow.
enrollment_urlPresent only when the prospect is eligible for at least one product
offered_productsPriced products with prefilled enrollment links. Empty until the renter has been underwritten in the enrollment flow.
application_submittedWhether the renter has submitted an application
latest_coverageThe renter's existing policy or cash deposit for this unit, if any
sdi_ineligibility_reasonsHuman-readable reasons SDA was declined. Omitted when there are none.
cd_ineligibility_reasonsHuman-readable reasons cash deposit was declined. Omitted when there are none.

Product objects

offered_products contains up to two entries:

security_deposit_insuranceupfront_premium_cents, monthly_premium_cents, upfront_enrollment_url, monthly_enrollment_url

cash_depositcash_deposit_amount_cents, enrollment_url

Prospect lifecycle

incomplete ──(all required fields present)──> processing ──(eligibility)──> ready
▲ │
└──────────────(a required field is cleared)──────────────────────────────┘
  • incomplete — required fields are missing. No enrollment URL.
  • processing — Rhino has what it needs and is checking eligibility. This is asynchronous and typically takes seconds.
  • readyenrollment_url is populated. Send the renter to Rhino.
  • 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 or cd_ineligibility_reasons. If it is ineligible for both, enrollment_url is omitted.

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 Rhino collects from the renter during enrollment, so offered_products and percent_savings stay empty until the renter has been through the flow. Do not build a UI that quotes Rhino 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"
}
}
StatusMeaning
401Request signature missing, malformed, or older than five minutes
404Unknown owner_slug, or the prospect does not exist on GET
422Validation 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 Rhino user along with the prospects and policies attached to it, so the same email can go through enrollment again. It returns 404 in production.

Note the path: unlike the rest of the API, this one is not scoped under /partners/{owner_slug}.

Next steps