Skip to main content

Leads Management

Use the Leads API to create, update, and delete leads from external systems and website forms.

Endpoints

POST /external/apis/v1.0/leads
DELETE /external/apis/v1.0/leads/:recordSystemId

All requests require an api-key header for a public API key configured with external access. The language header is optional and defaults to en-us.

Create And Upsert Contract

POST /external/apis/v1.0/leads accepts the old flat payload and the newer data payload. All request fields are optional.

Old Flat Payload

{
"name": "John Doe",
"phoneNumber": "+201001234567",
"email": "[email protected]",
"project": "North Coast"
}

New Data Payload

{
"recordSystemId": "website-form-uuid-123",
"data": {
"name": "John Doe",
"phoneNumber": "+201001234567",
"email": "[email protected]",
"project": "North Coast"
},
"additionalData": {
"externalScore": 87,
"tags": ["vip", "returning"]
}
}

Mixed Payload

{
"recordSystemId": "website-form-uuid-123",
"name": "Fallback Name",
"phoneNumber": "+201001234567",
"data": {
"name": "John Doe",
"campaign": "spring-launch"
}
}

When the same field exists in both places, data wins. In the mixed example above, the lead name is stored as John Doe.

FieldTypeRequiredDescription
recordSystemIdstringNoStable external lead or submission ID. If present, Sakneen upserts by this exact value within your organization. The server does not normalize it.
dataobjectNoArbitrary lead fields to store on the lead, such as name, phoneNumber, phoneNumber2, email, project, leadSource, or organization-specific fields.
additionalDataobjectNoFree-form metadata stored separately from lead data. Sakneen stores it as-is, merges it across updates, and does not use it for client, assignment, or compound logic.
namestringNoOld flat lead name field.
emailstringNoOld flat email field. Must be a valid email when sent.
phoneNumberstringNoOld flat phone number field.
projectstringNoOld flat project field. If present, Sakneen uses it to link existing organization compounds by translated compound name for the request language.

If recordSystemId is sent, the same value creates once and then updates that same non-deleted lead for the same organization. If recordSystemId is omitted, every request creates a new lead and cannot be updated or deleted later by external ID.

For direct website forms, generate recordSystemId only when you need retry-safe upsert or delete behavior. Recommended formats are <source>-<uuid> or <source>-<provider_submission_id>. Retries must reuse the exact same value.

When recordSystemId is present, Sakneen enforces one active lead per organization and recordSystemId. Concurrent first-time requests with the same value will not create duplicate leads.

Behavior

  • Sakneen creates or updates the client using your organization's existing client settings.
  • Sakneen checks phoneNumber and phoneNumber2 with the same duplicate-phone rules used by admin and sales lead creation. If your organization allows conflict leads, duplicate-phone checks are skipped. If conflict leads are not allowed, duplicates against non-deleted leads in the same organization return 400.
  • Sakneen links compounds by searching existing organization compounds whose translated name matches the provided project value case-insensitively.
  • Compound matching uses the request language header, defaulting to en-us.
  • Sakneen does not accept compound IDs in the request and does not create compounds from this endpoint.
  • Send project when compound linking is needed.
  • Sakneen uses data.leadSource only when you send it. The server does not inject a default lead source.
  • Partial updates merge into existing lead data.
  • Partial updates also merge additionalData into existing lead additionalData.
  • Existing lead leadId, assignment, parent state, and active state are preserved on update.
  • Successful changes are logged with External Lead Create, External Lead Update, or External Lead Delete, using modifier External API and source API.

This endpoint does not require a PUBLIC_CREATE_LEAD form, does not run required-field form validation, and does not reject unknown lead data fields.

DTO validation still applies: recordSystemId, flat name, flat phoneNumber, and flat project must be non-empty strings when sent; flat email must be a valid email when sent; data and additionalData must be objects when sent.

Examples

Create From Old Payload

curl -X POST "https://your-domain/external/apis/v1.0/leads" \
-H "api-key: your-api-key" \
-H "Content-Type: application/json" \
-H "language: en-us" \
-d '{
"name": "Jane Smith",
"phoneNumber": "+201001234567",
"email": "[email protected]",
"project": "North Coast"
}'

CRM Lead Upsert

Use the lead ID from your CRM as recordSystemId.

curl -X POST "https://your-domain/external/apis/v1.0/leads" \
-H "api-key: your-api-key" \
-H "Content-Type: application/json" \
-H "language: en-us" \
-d '{
"recordSystemId": "crm-lead-00163EAB688B",
"data": {
"name": "Jane Smith",
"phoneNumber": "+201001234567",
"email": "[email protected]",
"leadSource": "CRM",
"project": "North Coast"
}
}'

Direct Website Form Upsert

For website forms that do not create a CRM record first, generate a stable submission ID before calling Sakneen if you need retries or updates.

curl -X POST "https://your-domain/external/apis/v1.0/leads" \
-H "api-key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"recordSystemId": "alkarma-website-form-018f9f29-4b8a-7c1a",
"data": {
"name": "Ahmed Hassan",
"phoneNumber": "+201001234568",
"email": "[email protected]",
"project": "North Coast"
},
"additionalData": {
"providerSubmissionId": "018f9f29-4b8a-7c1a",
"campaign": "summer-launch"
}
}'

Update Existing Lead

Send the same recordSystemId with changed fields.

{
"recordSystemId": "alkarma-website-form-018f9f29-4b8a-7c1a",
"data": {
"phoneNumber": "+201001234568",
"email": "[email protected]"
},
"additionalData": {
"lastSyncedFrom": "website"
}
}

Delete By External ID

curl -X DELETE "https://your-domain/external/apis/v1.0/leads/alkarma-website-form-018f9f29-4b8a-7c1a" \
-H "api-key: your-api-key"

Delete only works for leads that have a recordSystemId. Delete is scoped to your organization. A missing lead returns 404.

JavaScript Example

const apiKey = process.env.SAKNEEN_API_KEY;
const baseDomain = process.env.SAKNEEN_DOMAIN;

async function sendLead(leadPayload) {
const response = await fetch(
`https://${baseDomain}/external/apis/v1.0/leads`,
{
method: "POST",
headers: {
"api-key": apiKey,
"Content-Type": "application/json",
language: "en-us",
},
body: JSON.stringify(leadPayload),
},
);

if (!response.ok) {
throw new Error(`Lead request failed with ${response.status}`);
}

return response.json();
}

await sendLead({
recordSystemId: "website-form-018f9f29-4b8a-7c1a",
data: {
name: "John Doe",
phoneNumber: "+201001234567",
email: "[email protected]",
},
});

Success Response

{
"ok": true,
"operation": "create",
"lead": {
"_id": "64f1234567890abcdef123456",
"leadId": 1001,
"recordSystemId": "website-form-018f9f29-4b8a-7c1a",
"compoundsIds": ["64f1234567890abcdef123450"],
"data": {
"name": "John Doe",
"phoneNumber": "+201001234567",
"email": "[email protected]",
"project": "North Coast"
},
"additionalData": {
"externalScore": 87,
"tags": ["vip", "returning"]
}
},
"newLead": {
"_id": "64f1234567890abcdef123456",
"leadId": 1001,
"recordSystemId": "website-form-018f9f29-4b8a-7c1a",
"compoundsIds": ["64f1234567890abcdef123450"],
"data": {
"name": "John Doe",
"phoneNumber": "+201001234567",
"email": "[email protected]",
"project": "North Coast"
},
"additionalData": {
"externalScore": 87,
"tags": ["vip", "returning"]
}
}
}

operation is create for a new lead and update when the same recordSystemId already exists for your organization. newLead is the same lead object kept for compatibility with earlier create responses.

Change Logs

Successful operations create lead change-log entries:

API actionChange contextOperationModifierSource
CreateExternal Lead CreatecreateExternal APIAPI
UpdateExternal Lead UpdateupdateExternal APIAPI
DeleteExternal Lead DeletedeleteExternal APIAPI

Common Errors

Invalid Email

{
"statusCode": 400,
"message": ["email must be an email"],
"error": "Bad Request"
}

Duplicate Phone Number

This error is returned only when your organization does not allow conflict leads and the submitted phoneNumber or phoneNumber2 already exists on another non-deleted lead in the same organization.

{
"statusCode": 400,
"message": [
"Duplicate Lead: Existing Lead (+201001234567) exists. and is not assigned to anyone."
],
"error": "Bad Request"
}

Missing Lead On Delete

{
"statusCode": 404,
"message": "Lead not found",
"error": "Not Found"
}

Integration Checklist

  • Use the flat payload when you only need create behavior.
  • Send recordSystemId when you need retry-safe upsert or delete behavior.
  • Reuse the exact same recordSystemId for retries and updates.
  • Put extra or organization-specific fields in data.
  • Use unique phoneNumber and phoneNumber2 values unless your organization allows conflict leads.
  • Put free-form metadata that should not affect lead/client logic in additionalData.
  • Send project when you want Sakneen to link matching existing compounds by translated compound name. Matching is case-insensitive. Do not send compound IDs.
  • Store Sakneen error responses in your integration logs for failed submissions.