Skip to content

Leads API

The Leads API provides endpoints for capturing contact form submissions, scheduling property tours, and managing leads. The system automatically routes leads to the correct brokerage based on the request domain.

Submit a contact form inquiry. The brokerage is automatically determined from the request domain.

POST /api/contact
Public
{
"firstName": "John",
"lastName": "Smith",
"email": "john.smith@example.com",
"phone": "208-555-0123",
"subject": "Interested in property at 123 Main St",
"message": "I'd like to learn more about this listing...",
"source_page": "/property/202412345",
"utm_source": "google",
"utm_medium": "organic",
"utm_campaign": "spring-2024"
}
FieldTypeRequiredDescription
firstNamestringYesContact’s first name
lastNamestringYesContact’s last name
emailstringYesValid email address
phonestringNoPhone number
subjectstringYesMessage subject
messagestringYesFull message content
source_pagestringNoPage URL where form was submitted
utm_sourcestringNoUTM tracking: source
utm_mediumstringNoUTM tracking: medium
utm_campaignstringNoUTM tracking: campaign
{
"success": true,
"message": "Thank you for your message! We'll get back to you within 24 hours.",
"lead_id": 456
}

Get a summary of lead counts by status for the current domain’s brokerage.

GET /api/leads/count
Auth Required
{
"new": 12,
"contacted": 8,
"qualified": 5,
"converted": 3,
"total": 28
}

Create a lead manually from the admin UI. Distinct from POST /api/contact: no Host-header brokerage lookup, no semantic-routing agent assignment. Staff supply their own context — including which agent to assign.

Used for walk-ins, phone calls, and any other inbound interest that didn’t come through a website form.

POST /api/v1/admin/leads
Auth Required
  • Non-admin users (brokers, agents, brokerage_owner) always create leads under their own user.brokerage_id. Any brokerage_id in the request body is ignored.
  • Platform admins may target any brokerage by passing brokerage_id in the body, or omit it to default to their own. The LeadAddDialog in /admin/leads renders a brokerage picker specifically for this case.
  • An unbrokeraged non-admin user gets a 400 Bad Request rather than silently leaking into a default tenant.
{
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"phone": "208-555-0123",
"subject": "Walk-in: interested in downtown condos",
"message": "Came into the office. Pre-approved up to $450k. Wants something within walking distance of the river.",
"assigned_agent_id": 42,
"status": "contacted",
"notes": "Followed up by phone same day.",
"brokerage_id": 2
}
FieldTypeRequiredDescription
first_namestringYesLead’s first name
last_namestringYesLead’s last name
emailstringYesValid email address
phonestringNoPhone number
subjectstringYesShort summary
messagestringYesFull notes from intake
assigned_agent_idintegerNoAgent to assign — must belong to the resolved brokerage
statusstringNoDefaults to "new". Set to e.g. "contacted" for already-underway intakes
notesstringNoInternal staff notes
brokerage_idintegerNoAdmin only. Target brokerage. Ignored for non-admin users

201 Created returns the full lead detail object (same shape as GET /api/v1/admin/leads/{lead_id}).

Manual-admin leads are tagged with source_domain: "admin-manual" and source_page: "/admin/leads" so analytics dashboards can split organic conversions from staff-entered ones.

StatusDetailWhen
400No brokerage context — cannot create leadAdmin omitted brokerage_id and has no own brokerage; or non-admin user has no brokerage_id set
400Agent {id} does not belong to brokerage {id}assigned_agent_id belongs to a different tenant
404Brokerage not found or disabledbrokerage_id doesn’t exist or has been soft-deleted

Schedule property viewing tours.

POST /api/tours
Public
{
"listing_id": "202412345",
"name": "Jane Doe",
"email": "jane@example.com",
"phone": "208-555-0456",
"preferred_date": "2024-12-30",
"preferred_time": "10:00 AM",
"message": "I'm interested in seeing this property. Is it available Saturday morning?"
}
FieldTypeRequiredDescription
listing_idstringYesMLS listing ID to tour
namestringYesRequester’s full name
emailstringYesContact email
phonestringNoContact phone
preferred_datestringNoPreferred tour date (YYYY-MM-DD)
preferred_timestringNoPreferred time slot
messagestringNoAdditional notes
{
"id": 789,
"listing_id": "202412345",
"name": "Jane Doe",
"email": "jane@example.com",
"status": "pending",
"created_at": "2024-12-28T15:30:00Z"
}

Get tour requests for the authenticated user’s brokerage.

GET /api/tours
Auth Required
ParameterTypeDescription
statusstringFilter by status (pending, confirmed, completed, cancelled)
listing_idstringFilter by property
[
{
"id": 789,
"listing_id": "202412345",
"name": "Jane Doe",
"email": "jane@example.com",
"phone": "208-555-0456",
"preferred_date": "2024-12-30",
"preferred_time": "10:00 AM",
"status": "pending",
"created_at": "2024-12-28T15:30:00Z"
}
]
GET /api/tours/{request_id}
Auth Required

Subscribe to a brokerage’s email newsletter.

POST /api/newsletter/subscribe
Public
{
"email": "subscriber@example.com",
"first_name": "John",
"interests": ["new-listings", "market-updates"]
}
{
"success": true,
"message": "Successfully subscribed to newsletter"
}

Leads progress through a standard pipeline:

StatusDescription
newJust received, not yet reviewed
contactedInitial contact made
qualifiedLead is qualified/interested
showingScheduled for property showings
offerMaking or negotiating offers
convertedSuccessfully closed
lostLead was lost or not interested

{
"detail": "Missing Host header"
}

Status: 400 Bad Request

{
"detail": "Brokerage not found"
}

Status: 404 Not Found

{
"detail": [
{
"loc": ["body", "email"],
"msg": "value is not a valid email address",
"type": "value_error.email"
}
]
}

Status: 422 Unprocessable Entity