Skip to content

Workflows

Idiomatic patterns for common Connect integrations. The endpoint-by-endpoint references live under Resources; this page is the cookbook.

Hit a few read endpoints once at startup and cache the IDs:

const headers = { Authorization: `Bearer ${TOKEN}` };
const [tables, calendars, appointmentTypes, resources] = await Promise.all([
fetch(`${API}/connect/tables/`, { headers }).then(r => r.json()),
fetch(`${API}/connect/calendar/`, { headers }).then(r => r.json()),
fetch(`${API}/connect/appointment_types/`, { headers }).then(r => r.json()),
fetch(`${API}/connect/resources/`, { headers }).then(r => r.json()),
]);
// For each table you'll write to, fetch its lead states
const leadPool = tables.find(t => t.name === 'Inbound');
const fullPool = await fetch(`${API}/connect/tables/${leadPool.id}`, { headers }).then(r => r.json());
const initialState = fullPool.leadStates.find(s => s.default);

Cache these — they don’t change often. Refresh on a schedule (daily) or invalidate when your integration hits a 404 on an ID.

// Triggered when your CRM gets a new lead
await fetch(`${API}/connect/table_entries/`, {
method: 'POST',
headers,
body: JSON.stringify({
tableId: leadPool.id,
leadStateId: initialState.id,
data: {
firstName: lead.firstName,
lastName: lead.lastName,
email: lead.email,
phone: lead.phone,
},
customFieldData: {
[crmIdFieldId]: lead.crmId,
},
}),
});

The created entry fires table.entry.added and table.entry.leadState.changed — your automations run automatically.

For dedup strategies, see Race conditions → Duplicate creates.

await fetch(`${API}/connect/table_entries/${entryId}`, {
method: 'PATCH',
headers,
body: JSON.stringify({ leadStateId: newState.id }),
});

Fires table.entry.leadState.changed only. Other fields untouched (see Merge patch).

The full call sequence:

// 1. Find an available slot in the requested window
const url = `${API}/connect/availability/?calendarId=${cal}&appointmentTypeId=${apt}&start=${start}&end=${end}`;
const { data } = await fetch(url, { headers }).then(r => r.json());
const slot = data.find(s => s.available);
if (!slot) throw new Error('No availability in requested window');
// 2. Create the lead (or use an existing one)
const entry = await fetch(`${API}/connect/table_entries/`, {
method: 'POST',
headers,
body: JSON.stringify({ tableId, leadStateId, data: leadData }),
}).then(r => r.json());
// 3. Book the slot, linking to the lead
await fetch(`${API}/connect/calendar_bookings/`, {
method: 'POST',
headers,
body: JSON.stringify({
calendarId: cal,
appointmentTypeId: slot.appointmentTypeId,
resourceId: slot.resourceId,
resourceSlotId: slot.resourceSlotId,
startDateTime: slot.startDateTime,
endDateTime: slot.endDateTime,
tableEntryId: entry.id,
}),
});

Wrap the booking call in a refetch-and-retry loop in case the slot fills in step 3 — see Race conditions → Slot-fill races.

There’s no reschedule endpoint. Delete + recreate:

await fetch(`${API}/connect/calendar_bookings/${oldId}`, { method: 'DELETE', headers });
await fetch(`${API}/connect/calendar_bookings/`, { method: 'POST', headers, body: JSON.stringify(newBooking) });

Fires calendar.appointment.deleted then calendar.appointment.booked — make your flows idempotent or use a transient lead state to suppress notifications during the swap.

// Setting a custom field's value to null deletes that key.
await fetch(`${API}/connect/table_entries/${entryId}`, {
method: 'PATCH',
headers,
body: JSON.stringify({
customFieldData: { [oldFieldId]: null },
}),
});

See Merge patch for the full semantics.