Skip to main content

Contact Lists Management

📊 Contact Export API

Constant Contact integration provides contact export functionality with cursor-based pagination and automatic token refresh.

📋 API Endpoints

MethodEndpointDescriptionAuth Required
GET/v1/integrations/constantcontact/listsExport contacts with pagination✅ JWT

🔧 Query Parameters

ParameterTypeDescriptionDefaultMax
limitIntegerRecords to return per page20500
pageStringCursor value for pagination--

📖 Export Contacts

Endpoint: GET /lists

Basic Request

GET /v1/integrations/constantcontact/lists
Authorization: Bearer {jwt_token}

Response

{
"success": true,
"message": "SUCCESS",
"data": [
{
"contact_id": "abc123-def456-ghi789",
"email_address": {
"address": "john.doe@example.com",
"permission_to_send": "implicit",
"created_at": "2020-06-12T01:49:23Z",
"updated_at": "2023-10-10T12:00:00Z",
"opt_in_source": "Contact",
"opt_in_date": "2020-06-12T01:49:23Z",
"confirm_status": "confirmed"
},
"first_name": "John",
"last_name": "Doe",
"job_title": "Marketing Manager",
"company_name": "Acme Corporation",
"phone_numbers": [
{
"phone_number": "+1-234-567-8900",
"kind": "mobile"
}
],
"street_addresses": [
{
"kind": "home",
"street": "123 Main St",
"city": "San Francisco",
"state": "CA",
"postal_code": "94105",
"country": "US"
}
],
"list_memberships": ["list-abc123", "list-def456"],
"taggings": [],
"notes": [],
"custom_fields": [
{
"custom_field_id": "field-123",
"value": "Premium Customer"
}
],
"created_at": "2020-06-12T01:49:23Z",
"updated_at": "2023-10-10T12:00:00Z",
"birthday_month": 6,
"birthday_day": 15,
"anniversary": "2015-06-12"
}
],
"pagination": {
"limit": 20,
"curr_page": null,
"next_page": "eyJpZCI6IjEwMCJ9",
"total": 1500,
"total_pages": 75
}
}

📊 Contact Fields

Core Fields

FieldTypeDescription
contact_idStringUnique contact identifier
email_addressObjectEmail information
first_nameStringFirst name
last_nameStringLast name
job_titleStringJob title
company_nameStringCompany name
created_atDateTimeContact created timestamp
updated_atDateTimeLast updated timestamp

Email Address Object

FieldTypeDescription
addressStringEmail address
permission_to_sendStringPermission level (implicit/explicit)
created_atDateTimeEmail created timestamp
updated_atDateTimeEmail updated timestamp
opt_in_sourceStringHow contact opted in
opt_in_dateDateTimeDate of opt-in
confirm_statusStringConfirmation status

Phone Numbers Array

[
{
"phone_number": "+1-234-567-8900",
"kind": "mobile" // mobile, work, home, fax, other
}
]

Street Addresses Array

[
{
"kind": "home", // home, work, other
"street": "123 Main St",
"city": "San Francisco",
"state": "CA",
"postal_code": "94105",
"country": "US"
}
]

List Memberships

Array of list IDs the contact belongs to:

["list-abc123", "list-def456", "list-ghi789"]

Custom Fields

Array of custom field values:

[
{
"custom_field_id": "field-123",
"value": "Premium Customer"
}
]

Optional Fields

FieldTypeDescription
birthday_monthIntegerBirth month (1-12)
birthday_dayIntegerBirth day (1-31)
anniversaryStringAnniversary date (YYYY-MM-DD)
taggingsArrayContact tags
notesArrayContact notes

📄 Pagination

Cursor-Based Pagination

Constant Contact uses cursor-based pagination (not page numbers):

First Page:

GET /v1/integrations/constantcontact/lists?limit=100
Authorization: Bearer {jwt_token}

Next Page (using cursor from response):

GET /v1/integrations/constantcontact/lists?limit=100&page=eyJpZCI6IjEwMCJ9
Authorization: Bearer {jwt_token}

Pagination Response

{
"pagination": {
"limit": 100,
"curr_page": "eyJpZCI6IjEwMCJ9",
"next_page": "eyJpZCI6IjIwMCJ9",
"total": 1500,
"total_pages": 15
}
}
// First request - no page parameter
let response = await fetch('/lists?limit=100');

// Subsequent requests - use next_page cursor
while (response.pagination.next_page) {
response = await fetch(`/lists?limit=100&page=${response.pagination.next_page}`);
}

🔄 Automatic Token Refresh

Before each contact export request:

  1. Retrieve Token: Fetch from database using account_id and uid
  2. Check Expiration: Compare generated_at timestamp
  3. Refresh if Needed: If expired (> 24 hours), refresh automatically
  4. Make API Call: Use fresh access token

Performance Impact: Token refresh adds ~200ms latency when needed.

📖 Usage Examples

Export All Contacts

GET /v1/integrations/constantcontact/lists?limit=500
Authorization: Bearer {jwt_token}

Export contacts in batches of 500 (maximum recommended).

Paginated Export

# First batch
GET /v1/integrations/constantcontact/lists?limit=100
Authorization: Bearer {jwt_token}

# Second batch (use next_page cursor from previous response)
GET /v1/integrations/constantcontact/lists?limit=100&page=eyJpZCI6IjEwMCJ9
Authorization: Bearer {jwt_token}

Progressive Loading

async function exportAllContacts() {
let allContacts = [];
let cursor = null;

do {
const url = cursor ? `/lists?limit=250&page=${cursor}` : `/lists?limit=250`;

const response = await fetch(url, {
headers: { Authorization: `Bearer ${jwt}` },
});

const data = await response.json();
allContacts.push(...data.data);
cursor = data.pagination.next_page;
} while (cursor);

return allContacts;
}

⚠️ Error Handling

ErrorStatusResponse
No credentials found404{ success: false, errno: 400, message: "Api Token Not Found" }
Unauthorized user401{ success: false, errno: 400, status: "Unauthorized User" }
Invalid limit400Error from Constant Contact API
Invalid cursor400Error from Constant Contact API
Token refresh failed400Error from Constant Contact API
Rate limit exceeded429Error from Constant Contact API

📝 Important Notes

  • 🔄 Auto Token Refresh: Access tokens refreshed automatically before request
  • 📊 Cursor Pagination: Use cursor values, not page numbers
  • 📈 Total Count: total field shows total contacts, not filtered count
  • 🎯 List Memberships: Included by default (include=list_memberships)
  • 👤 Contact Count: Response includes contacts_count for pagination
  • Performance: Optimal page size is 100-250 records
  • 🔒 Authentication: JWT token required for all requests

🚀 Best Practices

For Large Exports

  1. Use Maximum Limit: Set limit=500 for fastest exports
  2. Implement Progress Tracking: Show progress for long exports
  3. Handle Errors Gracefully: Retry failed requests
  4. Cursor Storage: Store last cursor to resume interrupted exports

For Performance

  1. Batch Processing: Process 250-500 contacts at a time
  2. Queue-Based: Use queue system for 10,000+ contact exports
  3. Parallel Processing: Process batches in parallel if order doesn't matter
  4. Cache Results: Cache frequently accessed contact lists

Example Queue Implementation

// Queue job for large contact export
{
type: 'constantcontact-export',
account_id: '12345',
user_id: 'user_abc',
batch_size: 250,
last_cursor: null, // Resume from last position
total_processed: 0
}

📊 API Limits

Rate Limiting

  • Requests per minute: Varies by plan
  • Typical limit: 10 requests/second
  • Recommendation: Add 100ms delay between requests

Page Size Limits

  • Minimum: 1 contact per page
  • Maximum: 500 contacts per page
  • Default: 20 contacts per page
  • Recommended: 100-250 for optimal performance

🎯 Common Scenarios

Initial Sync

Export all contacts for first-time sync:

# Export in large batches
GET /lists?limit=500

Process cursor values until next_page is null.

Incremental Updates

For regular updates, export in smaller batches:

GET /lists?limit=100

Filtered Export

Currently, the integration doesn't support filtering. To filter:

  1. Export all contacts
  2. Filter client-side by list membership, custom fields, etc.

List-Specific Export

To get contacts from specific list:

  1. Export all contacts
  2. Filter by list_memberships field
  3. Match against target list ID

🐛 Troubleshooting

"Api Token Not Found"

No credentials stored. User must authenticate first via OAuth flow.

"Unauthorized User"

JWT token missing or invalid. Check authentication headers.

Invalid Cursor Error

Cursor may be expired or corrupted. Restart export from beginning.

Token Refresh Failures

Refresh token may be revoked. User must re-authenticate.

Empty Results

  • Check if account has any contacts
  • Verify list memberships are included in request
  • Confirm API permissions in Constant Contact dashboard
💬

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:30 AM