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