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