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"
}
Field Type Required Description
firstName string Yes Contact’s first name
lastName string Yes Contact’s last name
email string Yes Valid email address
phone string No Phone number
subject string Yes Message subject
message string Yes Full message content
source_page string No Page URL where form was submitted
utm_source string No UTM tracking: source
utm_medium string No UTM tracking: medium
utm_campaign string No UTM 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
}
Field Type Required Description
first_name string Yes Lead’s first name
last_name string Yes Lead’s last name
email string Yes Valid email address
phone string No Phone number
subject string Yes Short summary
message string Yes Full notes from intake
assigned_agent_id integer No Agent to assign — must belong to the resolved brokerage
status string No Defaults to "new". Set to e.g. "contacted" for already-underway intakes
notes string No Internal staff notes
brokerage_id integer No Admin 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.

Status Detail When
400 No brokerage context — cannot create lead Admin omitted brokerage_id and has no own brokerage; or non-admin user has no brokerage_id set
400 Agent {id} does not belong to brokerage {id} assigned_agent_id belongs to a different tenant
404 Brokerage not found or disabled brokerage_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?"
}
Field Type Required Description
listing_id string Yes MLS listing ID to tour
name string Yes Requester’s full name
email string Yes Contact email
phone string No Contact phone
preferred_date string No Preferred tour date (YYYY-MM-DD)
preferred_time string No Preferred time slot
message string No Additional 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
Parameter Type Description
status string Filter by status (pending, confirmed, completed, cancelled)
listing_id string Filter 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:

Status Description
new Just received, not yet reviewed
contacted Initial contact made
qualified Lead is qualified/interested
showing Scheduled for property showings
offer Making or negotiating offers
converted Successfully closed
lost Lead 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