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.
| Field | Type | Required | Description |
|---|---|---|---|
recordSystemId | string | No | Stable external lead or submission ID. If present, Sakneen upserts by this exact value within your organization. The server does not normalize it. |
data | object | No | Arbitrary lead fields to store on the lead, such as name, phoneNumber, phoneNumber2, email, project, leadSource, or organization-specific fields. |
additionalData | object | No | Free-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. |
name | string | No | Old flat lead name field. |
email | string | No | Old flat email field. Must be a valid email when sent. |
phoneNumber | string | No | Old flat phone number field. |
project | string | No | Old 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
phoneNumberandphoneNumber2with 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 return400. - Sakneen links compounds by searching existing organization compounds whose translated name matches the provided
projectvalue case-insensitively. - Compound matching uses the request
languageheader, defaulting toen-us. - Sakneen does not accept compound IDs in the request and does not create compounds from this endpoint.
- Send
projectwhen compound linking is needed. - Sakneen uses
data.leadSourceonly when you send it. The server does not inject a default lead source. - Partial updates merge into existing lead
data. - Partial updates also merge
additionalDatainto existing leadadditionalData. - Existing lead
leadId, assignment, parent state, and active state are preserved on update. - Successful changes are logged with
External Lead Create,External Lead Update, orExternal Lead Delete, using modifierExternal APIand sourceAPI.
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 action | Change context | Operation | Modifier | Source |
|---|---|---|---|---|
| Create | External Lead Create | create | External API | API |
| Update | External Lead Update | update | External API | API |
| Delete | External Lead Delete | delete | External API | API |
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
recordSystemIdwhen you need retry-safe upsert or delete behavior. - Reuse the exact same
recordSystemIdfor retries and updates. - Put extra or organization-specific fields in
data. - Use unique
phoneNumberandphoneNumber2values unless your organization allows conflict leads. - Put free-form metadata that should not affect lead/client logic in
additionalData. - Send
projectwhen 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.