API Reference
Campaign API
Collect contacts from anywhere using our simple REST API.
Overview
The Campaign API allows you to programmatically add contacts to your campaigns from any source.
Use Cases
- Newsletter signup forms
- Landing page lead capture
- E-commerce customer lists
- Event registration
Features
- Simple REST API
- Automatic duplicate detection
- Email validation
- JSON & Form Data support
Authentication
The Campaign API uses API keys for authentication. Include your API key in every request.
Getting your API key
Navigate to Emails → Campaigns, click any campaign, then click the "Integration" button.
Security Warning
Never expose your API key in client-side code or commit it to version control. Use environment variables or server-side requests.
Endpoint
POST
/api/public/contactsAdd a new contact to a campaign.
Required Parameters
emailSubscriber's email address (must be valid format)
campaignIdYour campaign ID from the Integration dialog
apiKeyYour team API key from the Integration dialog
Optional Parameters
nameSubscriber's name (helps personalize emails)
Content Types
application/json
multipart/form-data
Code Examples
Choose your preferred language or framework.
HTML / JavaScript
Simple form with vanilla JavaScript - perfect for static websites.
newsletter-form.html
<!-- Simple HTML Form -->
<form id="newsletter-form">
<input type="email" name="email" placeholder="Enter your email" required />
<input type="text" name="name" placeholder="Your name (optional)" />
<button type="submit">Subscribe</button>
</form>
<script>
document.getElementById('newsletter-form').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
formData.append('campaignId', 'YOUR_CAMPAIGN_ID');
formData.append('apiKey', 'YOUR_API_KEY');
try {
const response = await fetch('https://yourdomain.com/api/public/contacts', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.success) {
alert('Successfully subscribed!');
e.target.reset();
} else {
alert('Error: ' + data.error);
}
} catch (error) {
alert('Failed to subscribe. Please try again.');
}
});
</script>Next.js (App Router)
Server-side API route for secure API key handling.
app/api/subscribe/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function POST(req: NextRequest) {
try {
const { email, name } = await req.json();
const response = await fetch('https://yourdomain.com/api/public/contacts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email,
name,
campaignId: process.env.CAMPAIGN_ID,
apiKey: process.env.API_KEY,
}),
});
const data = await response.json();
return NextResponse.json(data);
} catch (error) {
return NextResponse.json(
{ error: 'Failed to subscribe' },
{ status: 500 }
);
}
}components/newsletter-form.tsx
'use client';
import { useState } from 'react';
export function NewsletterForm() {
const [email, setEmail] = useState('');
const [name, setName] = useState('');
const [loading, setLoading] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
try {
const response = await fetch('/api/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, name }),
});
const data = await response.json();
if (data.success) {
alert('Successfully subscribed!');
setEmail('');
setName('');
} else {
alert('Error: ' + data.error);
}
} catch (error) {
alert('Failed to subscribe');
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your email"
required
/>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Your name (optional)"
/>
<button type="submit" disabled={loading}>
{loading ? 'Subscribing...' : 'Subscribe'}
</button>
</form>
);
}React
Client-side React component with fetch API.
NewsletterForm.tsx
import { useState } from 'react';
export function NewsletterForm() {
const [email, setEmail] = useState('');
const [name, setName] = useState('');
const [loading, setLoading] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
try {
const response = await fetch('https://yourdomain.com/api/public/contacts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email,
name,
campaignId: 'YOUR_CAMPAIGN_ID',
apiKey: 'YOUR_API_KEY', // ⚠️ Use backend proxy in production
}),
});
const data = await response.json();
if (data.success) {
alert('Successfully subscribed!');
setEmail('');
setName('');
} else {
alert('Error: ' + data.error);
}
} catch (error) {
alert('Failed to subscribe');
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your email"
required
/>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Your name (optional)"
/>
<button type="submit" disabled={loading}>
{loading ? 'Subscribing...' : 'Subscribe'}
</button>
</form>
);
}Vue.js
Vue 3 Composition API example.
NewsletterForm.vue
<script setup>
import { ref } from 'vue';
const email = ref('');
const name = ref('');
const loading = ref(false);
const handleSubmit = async () => {
loading.value = true;
try {
const response = await fetch('https://yourdomain.com/api/public/contacts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: email.value,
name: name.value,
campaignId: 'YOUR_CAMPAIGN_ID',
apiKey: 'YOUR_API_KEY', // ⚠️ Use backend proxy in production
}),
});
const data = await response.json();
if (data.success) {
alert('Successfully subscribed!');
email.value = '';
name.value = '';
} else {
alert('Error: ' + data.error);
}
} catch (error) {
alert('Failed to subscribe');
} finally {
loading.value = false;
}
};
</script>
<template>
<form @submit.prevent="handleSubmit">
<input
v-model="email"
type="email"
placeholder="Enter your email"
required
/>
<input
v-model="name"
type="text"
placeholder="Your name (optional)"
/>
<button type="submit" :disabled="loading">
{{ loading ? 'Subscribing...' : 'Subscribe' }}
</button>
</form>
</template>PHP
Server-side PHP with cURL.
subscribe.php
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = $_POST['email'] ?? '';
$name = $_POST['name'] ?? '';
$data = [
'email' => $email,
'name' => $name,
'campaignId' => 'YOUR_CAMPAIGN_ID',
'apiKey' => 'YOUR_API_KEY',
];
$ch = curl_init('https://yourdomain.com/api/public/contacts');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$result = json_decode($response, true);
if ($httpCode === 200 && $result['success']) {
echo json_encode(['success' => true, 'message' => 'Subscribed successfully']);
} else {
echo json_encode(['success' => false, 'error' => $result['error'] ?? 'Unknown error']);
}
}
?>cURL
Command-line example for testing.
test-api.sh
curl -X POST https://yourdomain.com/api/public/contacts \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"name": "John Doe",
"campaignId": "YOUR_CAMPAIGN_ID",
"apiKey": "YOUR_API_KEY"
}'Python
Python with requests library.
subscribe.py
import requests
def add_contact(email, name=None):
url = 'https://yourdomain.com/api/public/contacts'
data = {
'email': email,
'name': name,
'campaignId': 'YOUR_CAMPAIGN_ID',
'apiKey': 'YOUR_API_KEY'
}
try:
response = requests.post(url, json=data)
response.raise_for_status()
result = response.json()
if result.get('success'):
print(f'Successfully added {email}')
return True
else:
print(f'Error: {result.get("error")}')
return False
except requests.exceptions.RequestException as e:
print(f'Request failed: {e}')
return False
# Usage
add_contact('user@example.com', 'John Doe')Google Sheets
Google Apps Script for spreadsheet integration.
Code.gs
function addContactFromSheet() {
var sheet = SpreadsheetApp.getActiveSheet();
var lastRow = sheet.getLastRow();
// Get email from column A and name from column B
var email = sheet.getRange(lastRow, 1).getValue();
var name = sheet.getRange(lastRow, 2).getValue();
var url = 'https://yourdomain.com/api/public/contacts';
var payload = {
'email': email,
'name': name,
'campaignId': 'YOUR_CAMPAIGN_ID',
'apiKey': 'YOUR_API_KEY'
};
var options = {
'method': 'post',
'contentType': 'application/json',
'payload': JSON.stringify(payload)
};
try {
var response = UrlFetchApp.fetch(url, options);
var result = JSON.parse(response.getContentText());
if (result.success) {
sheet.getRange(lastRow, 3).setValue('✓ Synced');
Logger.log('Contact added successfully');
} else {
sheet.getRange(lastRow, 3).setValue('✗ Error: ' + result.error);
Logger.log('Error: ' + result.error);
}
} catch (error) {
sheet.getRange(lastRow, 3).setValue('✗ Failed');
Logger.log('Request failed: ' + error);
}
}
// Optional: Add a custom menu
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Surgio')
.addItem('Add Latest Contact', 'addContactFromSheet')
.addToUi();
}Responses
Success Response
{
"success": true,
"recipient": {
"id": "clx1234567890",
"email": "user@example.com",
"name": "John Doe",
"createdAt": "2024-12-07T12:00:00.000Z"
}
}Error Response
{
"success": false,
"error": "Invalid email format"
}Common Errors
Invalid email formatThe provided email is not validMissing required fieldsemail, campaignId, or apiKey is missingInvalid API keyThe API key is incorrect or expiredCampaign not foundThe campaign ID doesn't existBest Practices
Security
- • Never expose API keys in client-side code
- • Use environment variables
- • Implement server-side proxy routes
- • Regenerate keys if compromised
Error Handling
- • Check response status codes
- • Show user-friendly messages
- • Implement retry logic
- • Log errors for debugging
User Experience
- • Validate email format client-side
- • Show loading states
- • Provide clear feedback
- • Clear form after success
Performance
- • Use async/await patterns
- • Handle timeouts gracefully
- • Debounce form submissions
- • Cache API responses when appropriate