Add single DNC entry
curl --request POST \
--url https://api.kakiyo.com/v1/dnc \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"url": "https://linkedin.com/in/johnsmith"
}
'import requests
url = "https://api.kakiyo.com/v1/dnc"
payload = { "url": "https://linkedin.com/in/johnsmith" }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({url: 'https://linkedin.com/in/johnsmith'})
};
fetch('https://api.kakiyo.com/v1/dnc', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.kakiyo.com/v1/dnc",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'url' => 'https://linkedin.com/in/johnsmith'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.kakiyo.com/v1/dnc"
payload := strings.NewReader("{\n \"url\": \"https://linkedin.com/in/johnsmith\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.kakiyo.com/v1/dnc")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"url\": \"https://linkedin.com/in/johnsmith\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.kakiyo.com/v1/dnc")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"url\": \"https://linkedin.com/in/johnsmith\"\n}"
response = http.request(request)
puts response.read_body{
"error": null,
"data": {
"$id": "dnc_12345abcde",
"teamId": "team_67890fghij",
"url": "https://linkedin.com/in/johnsmith",
"$createdAt": "2025-11-18T10:30:00.000Z",
"$updatedAt": "2025-11-18T10:30:00.000Z"
},
"message": "Added to Do Not Contact list"
}{
"error": "validation_error",
"message": "The request parameters failed validation",
"details": [
{
"field": "name",
"message": "The name field is required"
}
]
}{
"error": "validation_error",
"message": "The request parameters failed validation",
"details": [
{
"field": "name",
"message": "The name field is required"
}
]
}{
"error": "validation_error",
"message": "The request parameters failed validation",
"details": [
{
"field": "name",
"message": "The name field is required"
}
]
}{
"error": "rate_limit_exceeded",
"message": "Too many requests. Please try again later.",
"resetTime": 1700308800000
}DNC
Add single DNC entry
Add a single LinkedIn URL to your team’s Do Not Contact list
POST
/
dnc
Add single DNC entry
curl --request POST \
--url https://api.kakiyo.com/v1/dnc \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"url": "https://linkedin.com/in/johnsmith"
}
'import requests
url = "https://api.kakiyo.com/v1/dnc"
payload = { "url": "https://linkedin.com/in/johnsmith" }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({url: 'https://linkedin.com/in/johnsmith'})
};
fetch('https://api.kakiyo.com/v1/dnc', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.kakiyo.com/v1/dnc",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'url' => 'https://linkedin.com/in/johnsmith'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.kakiyo.com/v1/dnc"
payload := strings.NewReader("{\n \"url\": \"https://linkedin.com/in/johnsmith\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.kakiyo.com/v1/dnc")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"url\": \"https://linkedin.com/in/johnsmith\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.kakiyo.com/v1/dnc")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"url\": \"https://linkedin.com/in/johnsmith\"\n}"
response = http.request(request)
puts response.read_body{
"error": null,
"data": {
"$id": "dnc_12345abcde",
"teamId": "team_67890fghij",
"url": "https://linkedin.com/in/johnsmith",
"$createdAt": "2025-11-18T10:30:00.000Z",
"$updatedAt": "2025-11-18T10:30:00.000Z"
},
"message": "Added to Do Not Contact list"
}{
"error": "validation_error",
"message": "The request parameters failed validation",
"details": [
{
"field": "name",
"message": "The name field is required"
}
]
}{
"error": "validation_error",
"message": "The request parameters failed validation",
"details": [
{
"field": "name",
"message": "The name field is required"
}
]
}{
"error": "validation_error",
"message": "The request parameters failed validation",
"details": [
{
"field": "name",
"message": "The name field is required"
}
]
}{
"error": "rate_limit_exceeded",
"message": "Too many requests. Please try again later.",
"resetTime": 1700308800000
}Overview
Add a single LinkedIn URL to your team’s Do Not Contact (DNC) list. Once added, this contact will be automatically excluded from all current and future campaigns, ensuring compliance with opt-out requests.Use Cases
- Opt-Out Requests: Add contacts who have requested to stop receiving messages
- Compliance Management: Maintain GDPR/CAN-SPAM compliance
- Manual Exclusions: Exclude specific individuals from campaigns
- Customer Requests: Honor removal requests from existing customers
- Integration Workflows: Add DNCs from external systems via API
Key Features
- Automatic URL Normalization: LinkedIn URLs automatically standardized
- Duplicate Prevention: Prevents adding the same URL twice
- Immediate Effect: Contact excluded from all campaigns instantly
- Cache Invalidation: Automatically updates cache for instant checks
- Rate Limited: 30 requests per minute for reliable performance
- Team Isolation: DNC entries only visible to your team
Testing Example
curl -X POST "https://api.kakiyo.com/v1/dnc" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://linkedin.com/in/johnsmith"
}'
// JavaScript/Node.js
const addToDNC = async (linkedinUrl) => {
const response = await fetch('https://api.kakiyo.com/v1/dnc', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: linkedinUrl
})
});
return await response.json();
};
// Usage example
const result = await addToDNC('https://linkedin.com/in/johnsmith');
if (result.error) {
console.log('❌ Failed to add:', result.error);
} else {
console.log('✅ Added to DNC list:', result.data);
}
# Python
import requests
def add_to_dnc(linkedin_url):
"""Add a LinkedIn URL to the DNC list"""
response = requests.post(
'https://api.kakiyo.com/v1/dnc',
json={'url': linkedin_url},
headers={
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
)
return response.json()
# Usage example
result = add_to_dnc('https://linkedin.com/in/johnsmith')
if result['error']:
print('❌ Failed to add:', result['error'])
else:
print('✅ Added to DNC list:', result['data'])
Request Body
Required Fields
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | LinkedIn profile URL to add to DNC list |
URL Format Support
The endpoint accepts various LinkedIn URL formats:https://linkedin.com/in/usernamehttps://www.linkedin.com/in/username/linkedin.com/in/usernamein/username
https://linkedin.com/in/username
Example Request Body
{
"url": "https://linkedin.com/in/johnsmith"
}
Response Format
Success Response (201 Created)
{
"error": null,
"data": {
"$id": "dnc_12345abcde",
"teamId": "team_67890fghij",
"url": "https://linkedin.com/in/johnsmith",
"$createdAt": "2025-11-18T10:30:00.000Z",
"$updatedAt": "2025-11-18T10:30:00.000Z"
},
"message": "Added to Do Not Contact list"
}
Error Responses
409 Conflict - URL Already on DNC List
{
"error": "dnc_entry_exists",
"message": "URL already on DNC list"
}
400 Bad Request - Missing URL
{
"error": "invalid_request",
"message": "LinkedIn URL is required"
}
400 Bad Request - Invalid URL Format
{
"error": "invalid_url",
"message": "Invalid LinkedIn URL format"
}
429 Too Many Requests - Rate Limit Exceeded
{
"error": "rate_limit_exceeded",
"message": "Too many requests. Please try again later.",
"resetTime": 1700308800000
}
401 Unauthorized - Invalid API Key
{
"error": "unauthorized",
"message": "Invalid or missing API key"
}
500 Internal Server Error
{
"error": "internal_error",
"message": "An internal error occurred"
}
URL Normalization
All LinkedIn URLs are automatically normalized before storage:Normalization Process
- Lowercase: Convert to lowercase
- Protocol: Add
https://if missing - Remove www: Strip
www.subdomain - Trim Trailing Slash: Remove trailing
/ - Extract Username: Extract username from various formats
Normalization Examples
| Input | Normalized Output |
|---|---|
https://www.linkedin.com/in/john/ | https://linkedin.com/in/john |
LINKEDIN.COM/IN/JOHN | https://linkedin.com/in/john |
linkedin.com/in/john | https://linkedin.com/in/john |
in/john | https://linkedin.com/in/john |
Rate Limiting
- Limit: 30 requests per minute per team
- Window: Rolling 60-second window
- Shared with: Other DNC write operations
- Exceeded: Returns 429 status with
resetTimetimestamp
Rate Limit Best Practices
- Implement Backoff: Wait for
resetTimebefore retrying - Batch Operations: Use Bulk Add for multiple URLs
- Queue System: Implement a queue for high-volume additions
- Error Handling: Gracefully handle 429 responses
Integration Examples
Handle Opt-Out Request
const handleOptOutRequest = async (linkedinUrl, reason) => {
try {
// Add to DNC list
const result = await addToDNC(linkedinUrl);
if (result.error === 'dnc_entry_exists') {
console.log('Already on DNC list');
return { success: true, alreadyExists: true };
}
if (result.error) {
console.error('Failed to add to DNC:', result.error);
return { success: false, error: result.error };
}
// Log the opt-out
await logOptOut({
url: linkedinUrl,
reason: reason,
timestamp: new Date().toISOString(),
dncId: result.data.$id
});
// Notify team
await notifyTeam(`Contact opted out: ${linkedinUrl}`);
return { success: true, entry: result.data };
} catch (error) {
console.error('Error handling opt-out:', error);
return { success: false, error: error.message };
}
};
// Usage
await handleOptOutRequest(
'linkedin.com/in/john',
'User replied with STOP'
);
Webhook Integration
const handleWebhookOptOut = async (webhookPayload) => {
const { contact, source } = webhookPayload;
// Validate payload
if (!contact.linkedinUrl) {
throw new Error('LinkedIn URL missing in webhook payload');
}
// Add to DNC
const result = await addToDNC(contact.linkedinUrl);
if (result.error && result.error !== 'dnc_entry_exists') {
throw new Error(`Failed to add to DNC: ${result.error}`);
}
// Log the source
await auditLog({
action: 'dnc_added',
source: source,
contact: contact.linkedinUrl,
timestamp: new Date().toISOString()
});
return result;
};
CRM Integration
const syncCRMOptOuts = async (crmClient) => {
// Get opt-outs from CRM
const optOuts = await crmClient.getOptOuts({
since: getLastSyncTime()
});
const results = {
added: 0,
alreadyExists: 0,
failed: 0,
errors: []
};
for (const optOut of optOuts) {
try {
const result = await addToDNC(optOut.linkedinUrl);
if (result.error === 'dnc_entry_exists') {
results.alreadyExists++;
} else if (result.error) {
results.failed++;
results.errors.push({
url: optOut.linkedinUrl,
error: result.error
});
} else {
results.added++;
}
// Rate limit protection
await new Promise(resolve => setTimeout(resolve, 2000));
} catch (error) {
results.failed++;
results.errors.push({
url: optOut.linkedinUrl,
error: error.message
});
}
}
// Update sync timestamp
await updateLastSyncTime(new Date().toISOString());
return results;
};
Form Submission Handler
const handleUnsubscribeForm = async (formData) => {
const { linkedinUrl, email, reason } = formData;
// Validate input
if (!linkedinUrl) {
return {
success: false,
message: 'LinkedIn URL is required'
};
}
// Add to DNC
const result = await addToDNC(linkedinUrl);
if (result.error === 'dnc_entry_exists') {
return {
success: true,
message: 'You have already been removed from our contact list'
};
}
if (result.error) {
return {
success: false,
message: 'An error occurred. Please try again later.'
};
}
// Send confirmation email
if (email) {
await sendEmail({
to: email,
subject: 'Unsubscribe Confirmation',
body: `You have been removed from our contact list.`
});
}
return {
success: true,
message: 'You have been successfully removed from our contact list'
};
};
Automated Campaign Monitoring
const monitorCampaignResponses = async (campaignId) => {
// Get campaign responses
const responses = await getCampaignResponses(campaignId);
// Check for opt-out keywords
const optOutKeywords = ['stop', 'unsubscribe', 'remove', 'opt out'];
for (const response of responses) {
const message = response.message.toLowerCase();
// Check if response contains opt-out keywords
const hasOptOut = optOutKeywords.some(keyword =>
message.includes(keyword)
);
if (hasOptOut) {
// Add to DNC
await addToDNC(response.prospect.linkedinUrl);
// Pause prospect in campaign
await pauseProspect(campaignId, response.prospect.id);
// Notify team
await notifyTeam({
type: 'opt_out_detected',
prospect: response.prospect.name,
campaign: campaignId,
message: response.message
});
}
}
};
Best Practices
- Validate URLs: Ensure LinkedIn URLs are valid before adding
- Handle Duplicates: Gracefully handle 409 responses (URL already exists)
- Immediate Action: Add to DNC as soon as opt-out is received
- Audit Trail: Log all DNC additions with timestamp and reason
- Team Notification: Alert team when contacts are added to DNC
- Bulk Operations: Use bulk endpoint for multiple URLs
- Error Handling: Implement proper error handling and retries
- Compliance: Document the reason for each DNC addition
Compliance Considerations
GDPR Compliance
- Right to Object: Honor opt-out requests within 24 hours
- Proof of Consent: Maintain audit logs of all DNC additions
- Data Retention: Keep DNC list indefinitely or per policy
- Transparency: Provide clear opt-out mechanisms
CAN-SPAM Compliance
- Opt-Out Mechanism: Provide clear unsubscribe links
- Honor Requests: Process opt-outs within 10 business days
- Permanent: Keep opt-outs permanent (don’t remove from DNC)
- No Charges: Never charge for processing opt-outs
Automatic Campaign Integration
Once a URL is added to the DNC list:- Active Campaigns: Contact is automatically paused in all active campaigns
- Future Campaigns: Contact cannot be added to new campaigns
- CSV Imports: DNC entries are skipped during prospect imports
- API Checks: All prospect creation endpoints check DNC list
- Worker Tasks: Agent tasks are automatically skipped for DNC contacts
Performance Considerations
- URL Normalization: Automatic, adds ~5ms to request time
- Duplicate Check: Uses indexed query, typically < 50ms
- Database Write: Typically < 100ms
- Cache Invalidation: Immediate, affects team-wide checks
- Team Isolation: Permissions enforced at database level
Response Fields
| Field | Type | Description |
|---|---|---|
error | string|null | Error code if failed, null on success |
data.$id | string | Unique identifier for the DNC entry |
data.teamId | string | Team ID that owns this entry |
data.url | string | Normalized LinkedIn URL |
data.$createdAt | string | ISO 8601 timestamp when entry was created |
data.$updatedAt | string | ISO 8601 timestamp when entry was last updated |
message | string | Success message |
Common Use Cases
Real-time Opt-Out Processing
const processOptOut = async (prospectId, linkedinUrl) => {
// 1. Add to DNC list
const dncResult = await addToDNC(linkedinUrl);
if (dncResult.error && dncResult.error !== 'dnc_entry_exists') {
throw new Error(`Failed to add to DNC: ${dncResult.error}`);
}
// 2. Pause in all campaigns
await pauseProspectInAllCampaigns(prospectId);
// 3. Log the action
await auditLog({
action: 'opt_out_processed',
prospectId: prospectId,
url: linkedinUrl,
timestamp: new Date().toISOString()
});
// 4. Send confirmation
return {
success: true,
message: 'Opt-out processed successfully',
dncId: dncResult.data.$id
};
};
Compliance Dashboard
const addToComplianceDashboard = async (linkedinUrl, reason) => {
// Add to DNC
const result = await addToDNC(linkedinUrl);
// Update compliance metrics
await updateMetrics({
type: 'dnc_addition',
timestamp: new Date().toISOString(),
reason: reason,
successful: !result.error
});
return result;
};
Next Steps
After adding a single DNC entry:- Verify Addition: Use Check DNC to confirm
- View All Entries: Use List DNC to see complete list
- Bulk Operations: Use Bulk Add for multiple URLs
- Monitor Campaigns: Ensure contact is paused in all active campaigns
Related Endpoints
- Check DNC Status - Check if a URL is on the DNC list
- List DNC Entries - View all DNC entries
- Bulk Add Entries - Add multiple URLs to DNC list
- Delete Entry - Remove a URL from DNC list
Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Body
application/json
LinkedIn profile URL to add to DNC list
Example:
"https://linkedin.com/in/johnsmith"
Last modified on November 18, 2025

