Skip to main content

Integration Examples

Practical examples of how to integrate with the Sakneen API.

Creating Or Upserting A Lead

The most common operation is saving a lead from a website, CRM, or partner system. Send recordSystemId when you want retry-safe upsert and delete behavior. Omit it when every request should create a new lead.

Basic Lead Upsert

const saveLead = async (leadPayload) => {
try {
const baseDomain = process.env.SAKNEEN_DOMAIN; // your assigned domain
const response = await fetch(
`https://${baseDomain}/external/apis/v1.0/leads`,
{
method: "POST",
headers: {
"api-key": process.env.SAKNEEN_API_KEY,
"Content-Type": "application/json",
language: "en-us",
},
body: JSON.stringify(leadPayload),
},
);

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

const result = await response.json();
console.log("Lead saved successfully:", result.lead);
return result;
} catch (error) {
console.error("Error saving lead:", error);
throw error;
}
};

// Usage
const leadPayload = {
recordSystemId: "website-form-uuid-002",
data: {
name: "Jane Smith",
phoneNumber: "+1987654321",
project: "Riverside Apartments",
email: "[email protected]",
leadSource: "Website",
},
additionalData: {
campaign: "summer-launch",
providerSubmissionId: "uuid-002",
},
};

saveLead(leadPayload);

Optional Client-Side Validation

Sakneen does not run PUBLIC_CREATE_LEAD required-field validation on this endpoint. Add local required-field checks only for fields your integration needs before sending the request. Sakneen still applies DTO validation and duplicate-phone rules for organizations that do not allow conflict leads.

const validateLead = (leadData) => {
const errors = [];
const data = { ...leadData, ...(leadData.data || {}) };

if (leadData.requiresUpsert && !leadData.recordSystemId) {
errors.push("recordSystemId is required for upsert/delete behavior");
}

if (!data.name || data.name.trim().length === 0) {
errors.push("Name is required");
}

if (!data.phoneNumber || !/^\+\d{10,15}$/.test(data.phoneNumber)) {
errors.push("Valid phone number is required");
}

if (data.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) {
errors.push("Invalid email format");
}

return errors;
};

const createLeadSafely = async (leadData) => {
// Validate input
const validationErrors = validateLead(leadData);
if (validationErrors.length > 0) {
throw new Error(`Validation failed: ${validationErrors.join(", ")}`);
}

return await saveLead(leadData);
};

Listing Units

Retrieve a paginated list of units with optional filtering and sorting.

Basic Unit Listing

const listUnits = async ({
page = 1,
limit = 10,
statuses,
sortBy,
order,
} = {}) => {
const baseDomain = process.env.SAKNEEN_DOMAIN;
const params = new URLSearchParams({ page, limit });

if (sortBy) params.append("sortBy", sortBy);
if (order) params.append("order", order);
if (statuses) {
for (const status of statuses) {
params.append("statuses", status);
}
}

const response = await fetch(
`https://${baseDomain}/external/apis/v1.0/units?${params}`,
{
method: "GET",
headers: { "api-key": process.env.SAKNEEN_API_KEY },
},
);

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

return await response.json();
};

// Usage - list available units, sorted by price ascending
const result = await listUnits({
page: 1,
limit: 20,
statuses: ["available"],
sortBy: "price",
order: "asc",
});

console.log(
`Page 1 of ${result.pagination.pages} (${result.pagination.total} total units)`,
);
result.data.forEach((unit) => {
console.log(
`${unit.unitId}: ${unit.title} - ${unit.price} ${unit.priceCurrency}`,
);
});

Paginate Through All Units

const getAllUnits = async (statuses) => {
const allUnits = [];
let page = 1;
let hasMore = true;

while (hasMore) {
const result = await listUnits({ page, limit: 100, statuses });
allUnits.push(...result.data);

hasMore = page < result.pagination.pages;
page++;
}

return allUnits;
};

// Fetch all available units
const availableUnits = await getAllUnits(["available"]);
console.log(`Total available units: ${availableUnits.length}`);

Getting a Single Unit

Retrieve a single unit by its slug for detail pages.

const getUnitBySlug = async (slug) => {
const baseDomain = process.env.SAKNEEN_DOMAIN;

const response = await fetch(
`https://${baseDomain}/external/apis/v1.0/units/${encodeURIComponent(slug)}`,
{
method: "GET",
headers: { "api-key": process.env.SAKNEEN_API_KEY },
},
);

if (response.status === 404) {
return null; // Unit not found
}

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

const result = await response.json();
return result.data;
};

// Usage
const unit = await getUnitBySlug("luxury-apartment-vh-12");
if (unit) {
console.log(`${unit.title} - ${unit.price} ${unit.priceCurrency}`);
console.log(`Bedrooms: ${unit.beds}, Bathrooms: ${unit.bathrooms}`);
console.log(`Status: ${unit.status}`);
console.log(`Property Type: ${unit.propertyType?.name}`);
} else {
console.log("Unit not found");
}

Error Handling

Comprehensive Error Handling

const handleApiResponse = async (response) => {
if (response.ok) {
return response.json();
}

let errorMessage = `HTTP ${response.status}: ${response.statusText}`;

try {
const errorData = await response.json();
if (errorData.message) {
errorMessage = Array.isArray(errorData.message)
? errorData.message
.map((m) => (typeof m === "object" ? JSON.stringify(m) : m))
.join(", ")
: errorData.message;
}
} catch (e) {
// If response is not JSON, use default error message
}

throw new Error(errorMessage);
};

const apiRequest = async (path, options = {}) => {
const baseDomain = process.env.SAKNEEN_DOMAIN;

try {
const response = await fetch(
`https://${baseDomain}/external/apis/v1.0${path}`,
{
...options,
headers: {
"api-key": process.env.SAKNEEN_API_KEY,
...options.headers,
},
},
);

return await handleApiResponse(response);
} catch (error) {
console.error(`API request failed [${path}]:`, error.message);
throw error;
}
};

// Usage
const units = await apiRequest("/units?page=1&limit=10");
const unit = await apiRequest("/units/luxury-apartment-vh-12");
const lead = await apiRequest("/leads", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
recordSystemId: "website-form-uuid-002",
data: {
name: "John Doe",
phoneNumber: "+1234567890",
project: "North Coast",
leadSource: "Website",
},
additionalData: {
landingPage: "/campaigns/summer",
},
}),
});

Webhook Integration

If you need to receive notifications when leads are processed:

const express = require("express");
const app = express();

app.use(express.json());

// Webhook endpoint to receive lead status updates
app.post("/webhook/lead-status", (req, res) => {
const { leadId, status, timestamp } = req.body;

console.log(`Lead ${leadId} status updated to: ${status} at ${timestamp}`);

// Process the webhook data
// Update your local database, send notifications, etc.

res.status(200).send("OK");
});

app.listen(3001, () => {
console.log("Webhook server listening on port 3001");
});

Best Practices

  1. Validate input data for your own integration rules before sending to the API
  2. Implement proper error handling for all API calls
  3. Use environment variables for sensitive data like API keys
  4. Reuse the same recordSystemId for retries when you want an upsert instead of a duplicate lead
  5. Log API interactions for debugging and monitoring
  6. Rate limit your requests to avoid overwhelming the API
  7. Use pagination efficiently -- prefer smaller page sizes (10-50) over the maximum (100)
  8. Cache unit slugs when building detail page links to avoid redundant list requests