APIs · base /api/inbound

Inbound 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.

Field mapping

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

POST/inbound/leadsscope leads:write · general key
FieldTypeRules
fullNamestringrequired, 1 to 120 characters
emailstringrequired, valid address, up to 254 characters
phonestringrequired, 7 to 40 characters; digits, spaces, +, -, ( ) accepted
addressstringrequired, 3 to 500 characters, full service address; used for de-duplication
smsConsentbooleanoptional; 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

POST/inbound/appointmentsscope appointments:write · general key
FieldTypeRules
leadIdstringrequired; from a lead you created or a webhook
projectIdstringoptional; when booking against a sold project
startsAt, endsAtISO-8601 UTCrequired; endsAt after startsAt
notesstringoptional, up to 500 characters; visible to the rep
idempotencyKeystringrequired, 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.

GET/inbound/jobs?take=50scope jobs:read · installer-contractor key

take 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" } ] }
POST/inbound/jobs/:id/statusscope jobs:status:write · installer-contractor key

Body { "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.