/api/inboundInbound API
Push leads and appointments into a company, and let a contractor inside that company pull its sold jobs. Authenticated with a general or installer-contractor key issued by the company.
If your system's field names differ, the company owner can attach a mapping profile to your key so you can post your native payload. Ask for one at onboarding rather than transforming on your side.
Leads
leads:write · general key| Field | Type | Rules |
|---|---|---|
fullName | string | required, 1 to 120 characters |
email | string | required, valid address, up to 254 characters |
phone | string | required, 7 to 40 characters; digits, spaces, +, -, ( ) accepted |
address | string | required, 3 to 500 characters, full service address; used for de-duplication |
smsConsent | boolean | optional; whether the customer agreed to text messages (see TCPA) |
Leads are attributed to the person the company chose when it created your key, so the right rep is notified.
POST /api/inbound/leads
x-api-key: <keyId>.<secret>
content-type: application/json
{ "fullName": "Pat Example", "email": "[email protected]", "phone": "801-555-0100",
"address": "123 Ridge Ave, Salt Lake City, UT 84101", "smsConsent": true }
201 Created { "status": "created", "leadId": "lead_…" }
200 OK { "status": "merged", "leadId": "lead_…" } // same address, open lead updated
200 OK { "status": "merged", "reason": "existing project", "projectId": "prj_…" } // already a sold project
De-duplication is by normalised address within the company. A merge updates contact details on the open lead and records your submission in the lead's history; it never overwrites a sold project. Re-sending the same lead is therefore safe.
Appointments
appointments:write · general key| Field | Type | Rules |
|---|---|---|
leadId | string | required; from a lead you created or a webhook |
projectId | string | optional; when booking against a sold project |
startsAt, endsAt | ISO-8601 UTC | required; endsAt after startsAt |
notes | string | optional, up to 500 characters; visible to the rep |
idempotencyKey | string | required, 8 to 160 characters, unique per booking on your side |
POST /api/inbound/appointments
{ "leadId": "lead_…", "startsAt": "2026-09-16T16:00:00Z", "endsAt": "2026-09-16T17:00:00Z",
"notes": "Gate code 4411", "idempotencyKey": "booking-7f3c2a" }
201 Created { "appointmentId": "apt_…" }
200 OK // replay with identical body returns the original appointment
409 Conflict // same idempotencyKey, different body
The platform emits appointment.booked to other subscribers when you book, so a calendar vendor and a CRM stay in step without talking to each other.
Contractor job pull
For a subcontractor set up as a contractor record inside one company. Companies that need scheduling, technicians and assignment writes use the Installer API v1 instead.
jobs:read · installer-contractor keytake 1 to 200, default 50. Returns sold projects assigned to your contractor record, newest change first.
{ "jobs": [ { "id": "prj_…", "workOrderNo": "WO-1042", "address": "123 Ridge Ave", "city": "Salt Lake City",
"state": "UT", "zip": "84101", "stage": "SCHEDULED", "installerNote": "Attic access via garage",
"lastUpdated": "2026-09-13T15:02:11.000Z" } ] }
jobs:status:write · installer-contractor keyBody { "note": "…" }, 1 to 2,000 characters. Appends a timestamped installer note to the job and notifies the company. Jobs outside your contractor record return 404.
Errors
Validation failures return 400 with the offending field named. A key of the wrong kind returns 401; a key missing the scope returns 403 naming the scope. Company-wide rate limit is 600 requests per minute; see Limits.