Skip to 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 ​

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

javascript
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 ​

javascript
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 ​

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

javascript
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 ​

javascript
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:

javascript
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