Skip to content

Broker lead API

Reference for the two broker lead endpoints. Request examples are real (captured from staging and current code); response examples are real shapes with PII replaced by placeholders. See Broker submits a lead for how these fit together.

Auth Authorization: Bearer <broker token> — Sanctum, ability access-broker-api
Rate limit 60 req/min per user/IP
Controller Api\Broker\LeadController@submitLead
Validated by App\Http\Requests\Broker\SubmitLeadRequest

Every field, verbatim from SubmitLeadRequest::rules():

Field Rules Notes
customer_inquiry required, in enum 1 investment · 2 unit-purchasing · 3 general. The form hardcodes 2.
first_name required, string
last_name required, string
mobile_country_code required, string e.g. "20"
phone required, string spaces stripped server-side
destination_slug required, exists:destinations,slug swapped to destination_source_id before the CRM call
phone_2 nullable, string
phone_3 nullable, string
email nullable, string ⚠️ no email-format rule — any string is accepted
budget nullable, numeric, min:0
interested_in_unit_id string, exists:units,id optional; mapped to the unit’s CRM source_id
portal_page nullable, in enum 1 unit · 2 destination · 3 compare · 4 talk-to-sales · 5 referral. Form hardcodes 1.
portal_comments nullable, string the form’s “Additional information”
preferred_communication_method nullable, in enum 1 virtual-meeting · 2 mobile · 3 email · 4 whatsapp
preferred_communication_time nullable, in enum 1 morning · 2 afternoon · 3 night
POST /api/broker/leads HTTP/1.1
Host: api.orascom.robustastudio.com
Authorization: Bearer <broker-sanctum-token>
Content-Type: application/json
{
"customer_inquiry": 2,
"portal_page": 1,
"destination_slug": "el-gouna",
"first_name": "Jane",
"last_name": "Doe",
"mobile_country_code": "20",
"phone": "1012345678",
"phone_2": null,
"email": "jane.doe@example.com",
"budget": 5000000,
"interested_in_unit_id": "1421",
"portal_comments": "Interested in a 3-bed",
"preferred_communication_method": 4,
"preferred_communication_time": 2
}

The backend double-wraps every response: an outer { data, message } plus an inner envelope.

200 — created (the inner message is empty; both source ids are returned):

{
"data": { "message": "", "data": { "lead_source_id": "CRM-LEAD-000123", "customer_source_id": "CRM-CONTACT-000123" }, "errors": null },
"message": "Success!"
}

422 — local leads-limit gate (inner data is a plain string — a reliable way to tell the local gate from a CRM error):

{ "data": "You have reached the maximum number of leads for this destination.", "message": "Failure!" }

422 — validation error — standard Laravel shape, not re-wrapped (validation exceptions bypass the success envelope): top-level { "message": "…", "errors": { "phone": ["…"] } }, no data.

400 — CRM error (the inner message carries the CRM/Logic-App reason).

Status Meaning Where it comes from
200 Lead created (one local audit row written on exact 200) CRM returned 200
422 Local leads-limit gate (top-level data string) or validation (top-level errors) Local — never hit the CRM
400 Logic App rejected the payload, or the middleware’s Azure-AD auth failed Normalized — the middleware turns every non-200 CRM response into 400, with the upstream body in the inner message
500 Middleware unreachable from the backend / a 300s timeout (ConnectionException), or the middleware’s “Unsupported Country” guard fired (see note) Infra, or a country not egypt/oman/montenegro (rare — country is derived from a validated destination)

What the backend forwards to the CRM middleware

Section titled “What the backend forwards to the CRM middleware”

SubmitNewLead forwards the whole validated body with three transforms, to POST {CRMS_MIDDLEWARE_API_URL}/brokers/{brokerSourceId}/leads, header country: <slug>:

  • destination_slugremoved, replaced by destination_source_id
  • interested_in_unit_id → the unit’s CRM source_id (or "")
  • phone → spaces removed

The middleware maps the unified fields to Dynamics lead entity fields (SubmitNewLeadRequestMapper). One schema is used for all three countries on the write path:

Dynamics field ⟵ unified field Dynamics field ⟵ unified field
firstname first_name clientbudget budget
lastname last_name customerinquiry customer_inquiry
email email portalcomments portal_comments
mobilecountrycode mobile_country_code portalpage portal_page
fullmobilenumber phone preferedcommunicationmethod preferred_communication_method
fullmobilenumber2 phone_2 preferedcommunicationtime preferred_communication_time
fullmobilenumber3 phone_3 salesagentid broker_source_id (the broker)
destinationid destination_source_id interestedin interested_in_unit_id
country numeric id injected by the middleware

Absent optional fields are sent to the Logic App as explicit null (they are not stripped).


GET /api/broker/leads — list the broker’s leads

Section titled “GET /api/broker/leads — list the broker’s leads”

Returns the broker’s leads split into active and past deals. Optional ?search=<phone>.

Auth Bearer, ability access-broker-api
Read path per-country direct Dynamics OData (not the Logic App)

Response shape (real, from staging — values are placeholders)

Section titled “Response shape (real, from staging — values are placeholders)”
{
"data": {
"data": {
"active_deals": [
{
"lead_source_id": "00000000-0000-0000-0000-000000000000",
"name": "<customer name>",
"email": "<email>",
"phone": "<phone>", "phone_2": null, "phone_3": null,
"status_code": "100000000",
"lead_status": "lead_submitted",
"country_slug": "egypt", "country_id": 1,
"destinationName": ["El-Gouna"],
"interested_in_unit_details": null,
"duplicate": false, "same_company": false,
"sales": null,
"lead_submitted_at": 1750428180,
"broker": { "id": 49, "name": "<broker name>" }
}
],
"past_deals": []
}
},
"message": "Success!"
}

lead_status is one of: lead_submitted, lead_verified, deal_closed, submitted_invoice, invoice_approved, lead_reserved, past_deals (backend DealStatusEnums). lead_submitted_at is a Unix epoch integer (or null), not a datetime string. The UI groups by broker when a manager has multiple agents, and renders each as a card.