Documentation Index
Fetch the complete documentation index at: https://docs.propal.io/llms.txt
Use this file to discover all available pages before exploring further.
This guide shows how to build a bi-directional sync between Propal leads and your external contact database.
Overview
A typical contact sync involves:
- Initial import — Pull all contacts from your external system and create them as leads in Propal
- Ongoing sync — Periodically check for new or updated contacts and sync them
- Reverse sync — Push new Propal leads back to your external system
Initial import
Fetch all contacts from your external system and create them in Propal:
async function importContacts(client, contacts) {
const results = { created: 0, skipped: 0, errors: [] };
for (const contact of contacts) {
try {
// Check if lead already exists
const { data: existing } = await client
.request(`/leads?search=${encodeURIComponent(contact.email)}&limit=1`)
.then(r => r.json());
if (existing.length > 0) {
results.skipped++;
continue;
}
// Create the lead
await client.request('/leads', {
method: 'POST',
body: JSON.stringify({
name: contact.name,
email: contact.email,
company: contact.company,
phone: contact.phone,
address: contact.address,
city: contact.city,
country: contact.country,
}),
});
results.created++;
} catch (error) {
results.errors.push({ contact: contact.email, error: error.message });
}
}
return results;
}
For large imports (1000+ contacts), use limit=100 per page and respect the rate limits. Consider running the import during off-peak hours.
Ongoing sync
Set up a periodic job (cron, scheduled task) to sync changes:
async function syncContacts(client, externalContacts) {
// 1. Fetch all Propal leads
const propalLeads = await fetchAllLeads(client);
// 2. Index by email for fast lookup
const leadsByEmail = new Map(
propalLeads
.filter(l => l.email)
.map(l => [l.email.toLowerCase(), l])
);
for (const contact of externalContacts) {
const existing = leadsByEmail.get(contact.email.toLowerCase());
if (existing) {
// Update if data has changed
if (hasChanges(existing, contact)) {
await client.request(`/leads/${existing.id}`, {
method: 'PATCH',
body: JSON.stringify({
name: contact.name,
company: contact.company,
phone: contact.phone,
}),
});
}
} else {
// Create new lead
await client.request('/leads', {
method: 'POST',
body: JSON.stringify({
name: contact.name,
email: contact.email,
company: contact.company,
}),
});
}
}
}
function hasChanges(lead, contact) {
return (
lead.name !== contact.name ||
lead.company !== contact.company ||
lead.phone !== contact.phone
);
}
Reverse sync — push Propal leads to your CRM
Fetch new leads from Propal and push them to your external system:
async function reverseSync(client, lastSyncTimestamp) {
// Fetch leads created after the last sync
const leads = await fetchAllLeads(client);
const newLeads = leads.filter(
lead => new Date(lead.created_at) > new Date(lastSyncTimestamp)
);
for (const lead of newLeads) {
await pushToExternalCRM({
name: lead.name,
email: lead.email,
company: lead.company,
phone: lead.phone,
source: 'Propal',
});
}
return newLeads.length;
}
Enriching leads with proposal data
For each lead, you can also fetch their associated proposals:
async function getLeadWithProposals(client, leadId) {
const [leadResponse, proposalsResponse] = await Promise.all([
client.request(`/leads/${leadId}`).then(r => r.json()),
client.request(`/leads/${leadId}/proposals`).then(r => r.json()),
]);
return {
...leadResponse,
proposals: proposalsResponse.data,
totalProposals: proposalsResponse.data.length,
hasAcceptedProposal: proposalsResponse.data.some(
p => p.deal_status === 'approved'
),
};
}
Best practices
- Deduplicate by email. Use the
search parameter to check for existing leads before creating duplicates.
- Handle rate limits. Add delays between requests during bulk operations. See Rate Limiting.
- Log sync results. Track created, updated, and skipped records for debugging.
- Use incremental sync. Only process records that changed since the last sync, not the entire dataset.