Contact Lists Management
📊 Contact Export API
Constant Contact integration provides contact export functionality with cursor-based pagination and automatic token refresh.
📋 API Endpoints
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
| GET | /v1/integrations/constantcontact/lists | Export contacts with pagination | ✅ JWT |
🔧 Query Parameters
| Parameter | Type | Description | Default | Max |
|---|---|---|---|---|
limit | Integer | Records to return per page | 20 | 500 |
page | String | Cursor 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
| Field | Type | Description |
|---|---|---|
contact_id | String | Unique contact identifier |
email_address | Object | Email information |
first_name | String | First name |
last_name | String | Last name |
job_title | String | Job title |
company_name | String | Company name |
created_at | DateTime | Contact created timestamp |
updated_at | DateTime | Last updated timestamp |
Email Address Object
| Field | Type | Description |
|---|---|---|
address | String | Email address |
permission_to_send | String | Permission level (implicit/explicit) |
created_at | DateTime | Email created timestamp |
updated_at | DateTime | Email updated timestamp |
opt_in_source | String | How contact opted in |
opt_in_date | DateTime | Date of opt-in |
confirm_status | String | Confirmation 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
| Field | Type | Description |
|---|---|---|
birthday_month | Integer | Birth month (1-12) |
birthday_day | Integer | Birth day (1-31) |
anniversary | String | Anniversary date (YYYY-MM-DD) |
taggings | Array | Contact tags |
notes | Array | Contact 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
}
}
Navigation Pattern
// 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:
- Retrieve Token: Fetch from database using account_id and uid
- Check Expiration: Compare
generated_attimestamp - Refresh if Needed: If expired (> 24 hours), refresh automatically
- 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
| Error | Status | Response |
|---|---|---|
| No credentials found | 404 | { success: false, errno: 400, message: "Api Token Not Found" } |
| Unauthorized user | 401 | { success: false, errno: 400, status: "Unauthorized User" } |
| Invalid limit | 400 | Error from Constant Contact API |
| Invalid cursor | 400 | Error from Constant Contact API |
| Token refresh failed | 400 | Error from Constant Contact API |
| Rate limit exceeded | 429 | Error 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:
totalfield shows total contacts, not filtered count - 🎯 List Memberships: Included by default (
include=list_memberships) - 👤 Contact Count: Response includes
contacts_countfor pagination - ⚡ Performance: Optimal page size is 100-250 records
- 🔒 Authentication: JWT token required for all requests
🚀 Best Practices
For Large Exports
- Use Maximum Limit: Set
limit=500for fastest exports - Implement Progress Tracking: Show progress for long exports
- Handle Errors Gracefully: Retry failed requests
- Cursor Storage: Store last cursor to resume interrupted exports
For Performance
- Batch Processing: Process 250-500 contacts at a time
- Queue-Based: Use queue system for 10,000+ contact exports
- Parallel Processing: Process batches in parallel if order doesn't matter
- 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
🔗 Related Documentation
🎯 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:
- Export all contacts
- Filter client-side by list membership, custom fields, etc.
List-Specific Export
To get contacts from specific list:
- Export all contacts
- Filter by
list_membershipsfield - 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