People API Guide

People API Guide

The People API is the core of Sure Send CRM, allowing you to manage contacts, leads, and customers programmatically. This guide covers everything you need to know about creating, updating, and organizing your contacts.

Overview

People (also called contacts or leads) represent individuals in your CRM. Each person can have:

  • Basic information (name, company)
  • Multiple emails, phones, and addresses
  • Tags for categorization
  • Custom fields for additional data
  • Pipeline stage and assignment
  • Source tracking

Creating People

Basic Person Creation

Endpoint: POST /api/partner/people

curl -X POST https://api.suresend.ai/api/partner/people \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "firstName": "Jane",
    "lastName": "Doe",
    "emails": [
      {
        "value": "[email protected]",
        "isPrimary": true
      }
    ]
  }'

Response:

{
  "id": "person-uuid",
  "firstName": "Jane",
  "lastName": "Doe",
  "name": "Jane Doe",
  "emails": [
    {
      "value": "[email protected]",
      "type": "work",
      "status": "active",
      "isPrimary": true
    }
  ],
  "phones": [],
  "addresses": [],
  "tags": [],
  "stage": null,
  "source": null,
  "createdAt": "2025-10-16T12:00:00Z",
  "updatedAt": "2025-10-16T12:00:00Z"
}

Full Contact with All Details

curl -X POST https://api.suresend.ai/api/partner/people \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "firstName": "John",
    "lastName": "Smith",
    "companyName": "Acme Corp",
    "stage": "Lead",
    "source": "Website Form",
    "averagePrice": 350000,
    "assignedUserId": "user-uuid",
    "emails": [
      {
        "value": "[email protected]",
        "type": "work",
        "isPrimary": true
      },
      {
        "value": "[email protected]",
        "type": "home"
      }
    ],
    "phones": [
      {
        "value": "+1-555-1234",
        "type": "mobile",
        "isPrimary": true
      },
      {
        "value": "+1-555-5678",
        "type": "work"
      }
    ],
    "addresses": [
      {
        "type": "work",
        "street": "123 Business St",
        "city": "Portland",
        "state": "OR",
        "code": "97201",
        "country": "US",
        "isPrimary": true
      }
    ],
    "tags": ["hot-lead", "buyer", "portland-area"]
  }'

Deduplication

Sure Send CRM automatically deduplicates contacts by email address:

  • If an email already exists, the API returns the existing person
  • Use PUT /people/{id} to update existing contacts
  • This prevents duplicate contacts in your CRM

Retrieving People

List All People

Endpoint: GET /api/partner/people

curl "https://api.suresend.ai/api/partner/people?page=1&limit=50" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Query Parameters:

  • page - Page number (default: 1)
  • limit - Items per page (default: 25, max: 100)
  • email - Filter by email address (partial, case-insensitive match)
  • phone - Filter by phone number (flexible formatting)
  • matchAny - Enable OR logic for filtering (e.g., matchAny=email,phone for lead deduplication)
  • includeTrash - Include soft-deleted records (default: false)

Response:

{
  "people": [
    {
      "id": "person-uuid",
      "firstName": "Jane",
      "lastName": "Doe",
      "name": "Jane Doe",
      "emails": [...],
      "phones": [...],
      "addresses": [...],
      "tags": ["buyer"],
      "stage": "Lead",
      "source": "Website"
    }
  ],
  "meta": {
    "total_count": 250,
    "current_page": 1,
    "total_pages": 5,
    "per_page": 50
  }
}

Get a Specific Person

Endpoint: GET /api/partner/people/{id}

curl https://api.suresend.ai/api/partner/people/person-uuid \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Search by Email

Endpoint: GET /api/partner/people?email={email}

Search for people by email address (partial, case-insensitive match):

curl "https://api.suresend.ai/api/partner/[email protected]" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

This will return any person with an email address matching "[email protected]". The search is:

Search by Phone

Endpoint: GET /api/partner/people?phone={phone}

Search for people by phone number with flexible formatting:

curl "https://api.suresend.ai/api/partner/people?phone=555-1234" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

The phone search normalizes digits and handles various formats:

  • phone=5551234 matches (555) 123-4567, 555-123-4567, +1-555-123-4567
  • Formatting characters (dashes, parentheses, spaces) are ignored
  • Partial matches supported - 555 will match all numbers containing "555"

Search with OR Logic (Lead Deduplication)

Endpoint: GET /api/partner/people?email={email}&phone={phone}&matchAny=email,phone

For lead deduplication use cases, you may want to find people matching either email or phone (instead of requiring both). Use the matchAny parameter:

curl "https://api.suresend.ai/api/partner/[email protected]&phone=555-1234&matchAny=email,phone" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

How it works:

  • matchAny=email,phone - Returns people matching the email OR the phone
  • Without matchAny - Returns only people matching BOTH email AND phone (default AND logic)

Use case: When receiving a new lead, check if they already exist by any contact method:

async function findExistingLead(email, phone) {
  const response = await axios.get(`${API_URL}/people`, {
    params: {
      email,
      phone,
      matchAny: 'email,phone'
    },
    headers: { Authorization: `Bearer ${API_TOKEN}` }
  });

  return response.data.people; // Returns all potential matches
}

Include Deleted Records

Endpoint: GET /api/partner/people?includeTrash=true

Include soft-deleted people in results:

curl "https://api.suresend.ai/api/partner/people?includeTrash=true" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

By default, soft-deleted records are excluded. Set includeTrash=true to include them.

Updating People

Partial Updates

Endpoint: PUT /api/partner/people/{id}

Only provide fields you want to change - all fields are optional:

curl -X PUT https://api.suresend.ai/api/partner/people/person-uuid \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "firstName": "Jane",
    "stage": "Qualified"
  }'

Updating Contact Information

⚠️

Important: Replace Behavior

Sending phones, emails, or addresses in a PUT request replaces ALL existing entries of that type. To update one entry while preserving others, you must include the complete list in your request.

For example, if a person has work, home, and mobile phones, and you want to add a fax number, include all four:

{
  "phones": [
    { "value": "+15551001", "type": "work", "isPrimary": true },
    { "value": "+15551002", "type": "home" },
    { "value": "+15551003", "type": "mobile" },
    { "value": "+15551004", "type": "fax" }
  ]
}

Sending only the fax number would delete the other three phones.

Update Email:

curl -X PUT https://api.suresend.ai/api/partner/people/person-uuid \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "emails": [
      {
        "value": "[email protected]",
        "type": "work",
        "isPrimary": true
      }
    ]
  }'

Update Phone:

curl -X PUT https://api.suresend.ai/api/partner/people/person-uuid \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "phones": [
      {
        "value": "+1-555-9999",
        "type": "mobile",
        "isPrimary": true
      }
    ]
  }'

Update Address:

curl -X PUT https://api.suresend.ai/api/partner/people/person-uuid \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "addresses": [
      {
        "type": "home",
        "street": "456 New St",
        "city": "Seattle",
        "state": "WA",
        "code": "98101",
        "isPrimary": true
      }
    ]
  }'

Working with Tags

Tags help you categorize and filter contacts. Common use cases:

  • Segmentation (buyer, seller, investor)
  • Lead quality (hot-lead, warm-lead, cold-lead)
  • Marketing campaigns (spring-2025, facebook-ad)
  • Behavioral tracking (viewed-property, requested-info)

Apply Tags (Add Without Removing)

Endpoint: POST /api/partner/people/{id}/tags/apply

curl -X POST https://api.suresend.ai/api/partner/people/person-uuid/tags/apply \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "tags": ["vip", "portland-buyer", "hot-lead"]
  }'

This adds the tags without removing existing ones.

Remove Tags

Endpoint: POST /api/partner/people/{id}/tags/delete

curl -X POST https://api.suresend.ai/api/partner/people/person-uuid/tags/delete \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "tags": ["cold-lead", "expired-campaign"]
  }'

Replace All Tags

Use PUT /people/{id} with mergeTags=false (default):

curl -X PUT "https://api.suresend.ai/api/partner/people/person-uuid?mergeTags=false" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "tags": ["active-buyer"]
  }'

Add Tags While Keeping Existing

Use PUT /people/{id} with mergeTags=true:

curl -X PUT "https://api.suresend.ai/api/partner/people/person-uuid?mergeTags=true" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "tags": ["new-tag"]
  }'

Email, Phone, and Address Types

Email Types

  • work - Work email
  • home - Personal email
  • other - Other email

Email Status

  • active - Valid, can receive emails
  • bounced - Email bounced, invalid
  • unsubscribed - User unsubscribed

Phone Types

  • mobile - Mobile/cell phone
  • work - Work phone
  • home - Home phone
  • other - Other phone

Phone Status

  • active - Valid, can receive calls/texts
  • disconnected - Phone number no longer valid
  • do-not-call - User requested no calls

Address Types

  • home - Home address
  • work - Work address
  • other - Other address

Primary Contact Methods

Each person can have one primary email, phone, and address:

{
  "emails": [
    {
      "value": "[email protected]",
      "isPrimary": true
    },
    {
      "value": "[email protected]",
      "isPrimary": false
    }
  ]
}

Pipeline Management

Stages

Track where contacts are in your sales pipeline:

Common stages:

  • Lead - New lead, not qualified
  • Qualified - Lead is qualified
  • Proposal - Proposal sent
  • Negotiation - In negotiation
  • Closed Won - Deal closed successfully
  • Closed Lost - Deal lost
curl -X PUT https://api.suresend.ai/api/partner/people/person-uuid \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "stage": "Qualified"
  }'

Assignment

Assign contacts to specific users:

curl -X PUT https://api.suresend.ai/api/partner/people/person-uuid \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "assignedUserId": "user-uuid"
  }'

Source Tracking

Track where leads come from:

curl -X PUT https://api.suresend.ai/api/partner/people/person-uuid \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "source": "Facebook Ad Campaign"
  }'

Custom Fields

Extend person records with custom data fields. See Custom Fields Guide for details.

Setting Custom Fields

curl -X PUT https://api.suresend.ai/api/partner/people/person-uuid \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "customFields": {
      "property_budget": 500000,
      "preferred_location": "Downtown Portland",
      "move_in_timeline": "3-6 months"
    }
  }'

Custom Fields in Response

When retrieving a person, custom field values are returned in two formats for flexibility:

  1. Flat format with custom prefix (Follow Up Boss compatible):

    • Field names are camelCased with a custom prefix
    • Example: customPropertyBudget, customPreferredLocation
  2. Nested customFields object (mirrors input format):

    • Field names use snake_case matching the input format
    • Example: customFields.property_budget, customFields.preferred_location

Example Response:

{
  "id": "person-uuid",
  "name": "John Doe",
  "firstName": "John",
  "lastName": "Doe",
  "emails": [...],
  "phones": [...],
  "customPropertyBudget": 500000,
  "customPreferredLocation": "Downtown Portland",
  "customMoveInTimeline": "3-6 months",
  "customFields": {
    "property_budget": 500000,
    "preferred_location": "Downtown Portland",
    "move_in_timeline": "3-6 months"
  }
}

Why two formats?

  • Flat format: Compatible with Follow Up Boss and easy to access top-level fields
  • Nested format: Mirrors the input format for simpler round-trip data handling

Deleting People

Endpoint: DELETE /api/partner/people/{id}

curl -X DELETE https://api.suresend.ai/api/partner/people/person-uuid \
  -H "Authorization: Bearer YOUR_API_TOKEN"

⚠️ Warning: Deleting a person is permanent and cannot be undone. All associated data (notes, tasks, events) will also be deleted.

Common Use Cases

1. Find Contact by Email

Check if a contact exists before creating:

const axios = require('axios');

const API_TOKEN = process.env.API_TOKEN;
const API_URL = 'https://api.suresend.ai/api/partner';

async function findOrCreateContact(email, contactData) {
  // Search for existing contact by email
  const searchResponse = await axios.get(`${API_URL}/people`, {
    params: { email },
    headers: { Authorization: `Bearer ${API_TOKEN}` }
  });

  if (searchResponse.data.people.length > 0) {
    // Contact exists, return the first match
    console.log('Contact found:', searchResponse.data.people[0].name);
    return searchResponse.data.people[0];
  }

  // Contact doesn't exist, create new
  const createResponse = await axios.post(`${API_URL}/people`, contactData, {
    headers: { Authorization: `Bearer ${API_TOKEN}` }
  });

  console.log('Contact created:', createResponse.data.name);
  return createResponse.data;
}

// Usage
findOrCreateContact('[email protected]', {
  firstName: 'John',
  lastName: 'Doe',
  emails: [{ value: '[email protected]', isPrimary: true }]
});

2. Import Contacts from CSV

const fs = require('fs');
const csv = require('csv-parser');
const axios = require('axios');

const API_TOKEN = process.env.API_TOKEN;
const API_URL = 'https://api.suresend.ai/api/partner';

async function importContacts(csvFile) {
  const contacts = [];

  fs.createReadStream(csvFile)
    .pipe(csv())
    .on('data', (row) => {
      contacts.push({
        firstName: row['First Name'],
        lastName: row['Last Name'],
        emails: [{ value: row['Email'], isPrimary: true }],
        phones: [{ value: row['Phone'], type: 'mobile', isPrimary: true }],
        companyName: row['Company'],
        source: 'CSV Import'
      });
    })
    .on('end', async () => {
      for (const contact of contacts) {
        try {
          await axios.post(`${API_URL}/people`, contact, {
            headers: { Authorization: `Bearer ${API_TOKEN}` }
          });
          console.log(`✓ Imported: ${contact.firstName} ${contact.lastName}`);
        } catch (error) {
          console.error(`✗ Failed: ${contact.firstName} ${contact.lastName}`, error.message);
        }
      }
    });
}

importContacts('contacts.csv');

2. Sync Contacts to External System

const axios = require('axios');

const API_TOKEN = process.env.API_TOKEN;
const API_URL = 'https://api.suresend.ai/api/partner';

async function syncContacts() {
  let page = 1;
  let hasMore = true;

  while (hasMore) {
    const response = await axios.get(`${API_URL}/people`, {
      params: { page, limit: 100 },
      headers: { Authorization: `Bearer ${API_TOKEN}` }
    });

    const { people, meta } = response.data;

    // Sync to your external system
    for (const person of people) {
      await syncToExternalSystem(person);
    }

    hasMore = page < meta.total_pages;
    page++;
  }
}

async function syncToExternalSystem(person) {
  // Your sync logic here
  console.log('Syncing:', person.name);
}

syncContacts();

3. Segment Contacts by Tags

async function getHotLeads() {
  let page = 1;
  let hotLeads = [];

  while (true) {
    const response = await axios.get(`${API_URL}/people`, {
      params: { page, limit: 100 },
      headers: { Authorization: `Bearer ${API_TOKEN}` }
    });

    const { people, meta } = response.data;

    // Filter by tag
    const filtered = people.filter(p =>
      p.tags && p.tags.includes('hot-lead')
    );

    hotLeads = hotLeads.concat(filtered);

    if (page >= meta.total_pages) break;
    page++;
  }

  return hotLeads;
}

getHotLeads().then(leads => {
  console.log(`Found ${leads.length} hot leads`);
});

4. Bulk Update Contact Stages

async function promoteQualifiedLeads() {
  const response = await axios.get(`${API_URL}/people`, {
    params: { limit: 100 },
    headers: { Authorization: `Bearer ${API_TOKEN}` }
  });

  const qualifiedLeads = response.data.people.filter(p =>
    p.stage === 'Qualified' && p.tags.includes('hot-lead')
  );

  for (const lead of qualifiedLeads) {
    await axios.put(`${API_URL}/people/${lead.id}`,
      { stage: 'Proposal' },
      { headers: { Authorization: `Bearer ${API_TOKEN}` } }
    );
    console.log(`Promoted ${lead.name} to Proposal stage`);
  }
}

5. Auto-Tag Based on Behavior

// Run this when you receive a webhook for page views
async function autoTagBasedOnBehavior(personId, pageUrl) {
  const tags = [];

  // Tag based on page viewed
  if (pageUrl.includes('/properties/luxury')) {
    tags.push('luxury-buyer');
  }
  if (pageUrl.includes('/properties/commercial')) {
    tags.push('commercial-buyer');
  }
  if (pageUrl.includes('/neighborhoods/downtown')) {
    tags.push('downtown-interest');
  }

  if (tags.length > 0) {
    await axios.post(
      `${API_URL}/people/${personId}/tags/apply`,
      { tags },
      { headers: { Authorization: `Bearer ${API_TOKEN}` } }
    );
    console.log(`Auto-tagged person ${personId}:`, tags);
  }
}

Best Practices

Data Quality

  1. Always provide primary contact method - Mark at least one email/phone as primary
  2. Validate email formats - Use regex or validation library before submitting
  3. Normalize phone numbers - Use consistent format (E.164 recommended)
  4. Use ISO 8601 date format - Send dates as YYYY-MM-DD (e.g., 2025-12-31) for custom date fields
  5. Clean up duplicates - Leverage deduplication by email
  6. Keep data current - Regularly update contact information

Performance

  1. Use pagination - Don't fetch all contacts at once
  2. Batch operations - Group updates when possible
  3. Cache frequently accessed data - Reduce API calls
  4. Use webhooks - Get real-time updates instead of polling
  5. Implement rate limiting - Respect API rate limits

Tagging Strategy

  1. Use consistent naming - Lowercase with hyphens (e.g., "hot-lead")
  2. Create tag taxonomy - Organize tags by category
  3. Don't over-tag - Keep tags focused and meaningful
  4. Clean up old tags - Remove outdated or unused tags
  5. Document tag meanings - Maintain a tag glossary

Security

  1. Never expose API tokens - Keep tokens secret
  2. Use environment variables - Don't hardcode credentials
  3. Rotate tokens regularly - Create new tokens periodically
  4. Limit token scope - Use team-specific tokens when possible
  5. Monitor API usage - Watch for suspicious activity

Error Handling

Common Errors

404 Not Found:

{
  "error": "Person not found"
}

422 Validation Error:

{
  "error": "Validation failed",
  "errors": [
    "First name can't be blank",
    "Email is invalid"
  ]
}

429 Rate Limit:

{
  "error": "Rate limit exceeded",
  "message": "Too many requests for context 'PUT.people'. Please retry after 3600 seconds.",
  "retry_after": 3600
}

Error Handling Example

async function createPerson(data) {
  try {
    const response = await axios.post(`${API_URL}/people`, data, {
      headers: { Authorization: `Bearer ${API_TOKEN}` }
    });
    return response.data;
  } catch (error) {
    if (error.response) {
      switch (error.response.status) {
        case 422:
          console.error('Validation errors:', error.response.data.errors);
          break;
        case 429:
          const retryAfter = error.response.data.retry_after;
          console.error(`Rate limited. Retry after ${retryAfter} seconds`);
          break;
        case 401:
          console.error('Invalid API token');
          break;
        default:
          console.error('API error:', error.response.data.error);
      }
    } else {
      console.error('Network error:', error.message);
    }
    throw error;
  }
}

Next Steps

Need Help?