
SMS Reminder API Documentation
Complete API reference for adding contacts and managing SMS reminder campaigns programmatically.
Quick Start
Get started with the Remindlo API in 3 steps:
Get your API key from Dashboard → Settings → API Keys
List your campaigns to get campaign IDs
Add contacts and enroll them in campaigns
Optionally, set up webhooks to receive real-time notifications when data changes.
# Example: Add a contact
curl -X POST "https://api.remindlo.co.uk/v1/contacts" \
-H "x-api-key: sk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"phone": "+447912345678",
"first_name": "John",
"marketing_consent": true
}'Authentication
All API requests require an API key passed in the x-api-key header:
x-api-key: sk_live_your_key_hereImportant: Keep your API key secure. Never expose it in client-side code or public repositories.
Base URL
https://api.remindlo.co.uk/v1Endpoints
GET /v1/campaigns
List all available SMS campaigns for your account.
curl -X GET "https://api.remindlo.co.uk/v1/campaigns" \
-H "x-api-key: sk_live_xxx"Response:
{
"campaigns": [
{
"id": "uuid",
"name": "Appointment Reminder",
"type": "recurring",
"status": "running"
}
]
}POST /v1/contacts
Create or update a contact. If a contact with the same phone or email exists, it will be updated.
Request Body
Field | Type | Required | Description |
|---|---|---|---|
phone | string | * | Phone in E.164 format (e.g., +447912345678) |
string | * | Email address | |
first_name | string | Contact's first name | |
last_name | string | Contact's last name | |
marketing_consent | boolean | Whether contact agreed to receive messages | |
next_due_at | string | Next appointment date (ISO 8601) | |
last_service_at | string | Last service date (ISO 8601) | |
note | string | Notes about the contact | |
tags | string[] | Tags for categorization | |
custom_fields | object | Custom data as JSON | |
campaign_ids | string[] | Campaign IDs to auto-enroll | |
is_recurrent | boolean | Mark contact as a recurrent service — next_due_at automatically advances by the interval each time the service date passes | |
recurrent_interval_value | number | Interval amount (1–999). Required when is_recurrent is true | |
recurrent_interval_unit | string |
|
* At least one of phone or email is required.
curl -X POST "https://api.remindlo.co.uk/v1/contacts" \
-H "x-api-key: sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"phone": "+447912345678",
"first_name": "John",
"last_name": "Smith",
"marketing_consent": true,
"next_due_at": "2025-03-15",
"campaign_ids": ["campaign-uuid"],
"is_recurrent": true,
"recurrent_interval_value": 12,
"recurrent_interval_unit": "months"
}'Response:
{
"success": true,
"contact_id": "uuid",
"action": "created",
"contact": {
"id": "uuid",
"first_name": "John",
"last_name": "Smith",
"phone": "+447912345678",
"marketing_consent": true,
"next_due_at": "2025-03-15",
"is_recurrent": true,
"recurrent_interval_value": 12,
"recurrent_interval_unit": "months"
},
"enrollments": [
{ "campaign_id": "campaign-uuid", "status": "enrolled" }
]
}POST /v1/campaigns-enroll
Enroll an existing contact in a campaign. For one-off campaigns that are running, the message is queued immediately. For recurring campaigns, the scheduler handles sending.
Request Body
Field | Type | Required | Description |
|---|---|---|---|
campaign_id | string | Yes | UUID of the campaign |
contact_id | string | Yes* | UUID of the contact to enroll (preferred) |
customer_id | string | Yes* | Legacy alias for contact_id — still accepted for backwards compatibility |
* Provide either contact_id (recommended) or customer_id. If both are provided, contact_id takes precedence.
curl -X POST "https://api.remindlo.co.uk/v1/campaigns-enroll" \
-H "x-api-key: sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"campaign_id": "campaign-uuid",
"contact_id": "contact-uuid"
}'Response:
{
"enrollment_id": "uuid",
"status": "enrolled"
}Possible status values:
enrolled— contact successfully enrolledalready_enrolled— contact was already in this campaignskipped— enrollment skipped (e.g., contact has no phone, or campaign is not active)
POST /v1/messages
Send a one-time SMS to an existing contact. Requires a paid plan. The message is queued immediately and sent via the configured SMS provider.
Request Body
Field | Type | Required | Description |
|---|---|---|---|
contact_id | string | Yes | UUID of the contact to message |
body | string | Yes | Message text (max 1600 characters) |
channel | string | No | Delivery channel. Default: |
curl -X POST "https://api.remindlo.co.uk/v1/messages" \
-H "x-api-key: sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"contact_id": "contact-uuid",
"body": "Hi John, just a quick reminder about your appointment tomorrow at 2pm."
}'Response (202 Accepted):
{
"success": true,
"message_id": "uuid",
"status": "queued",
"contact_id": "contact-uuid",
"parts": 1,
"channel": "sms"
}Error responses:
403 PLAN_REQUIRED— sending one-time messages requires a paid plan404 CONTACT_NOT_FOUND— contact does not exist or has no phone number429 SMS_LIMIT_EXCEEDED— SMS quota has been exceeded
GET /v1/contacts
List contacts with filtering and pagination.
Query Parameters
Parameter | Type | Description |
|---|---|---|
limit | number | Max results (default 50, max 100) |
offset | number | Skip first N results |
search | string | Search in name, phone, email |
has_phone | boolean | Only contacts with phone |
marketing_consent | boolean | Filter by consent |
next_due_before | string | Due date before (ISO 8601) |
next_due_after | string | Due date after (ISO 8601) |
sort_by | string | created_at, updated_at, next_due_at, first_name |
sort_order | string | asc or desc |
is_recurrent | boolean | Filter by recurrent status (true / false) |
created_after | string | Only contacts created after this date (ISO 8601) |
curl -X GET "https://api.remindlo.co.uk/v1/contacts?limit=10&search=john" \
-H "x-api-key: sk_live_xxx"GET /v1/contacts/:id
Get a single contact by ID, phone, or email.
# By ID
curl -X GET "https://api.remindlo.co.uk/v1/contacts/uuid" \
-H "x-api-key: sk_live_xxx"
# By phone
curl -X GET "https://api.remindlo.co.uk/v1/contacts?phone=+447912345678" \
-H "x-api-key: sk_live_xxx"Error Handling
All errors return a consistent JSON format:
{
"success": false,
"error": {
"code": "INVALID_PHONE_FORMAT",
"message": "Phone number must be in E.164 format",
"details": {
"field": "phone",
"value": "07912345678",
"suggestion": "Use format +447912345678 for UK numbers"
}
}
}Error Codes
HTTP | Code | Description |
|---|---|---|
400 | INVALID_PHONE_FORMAT | Phone not in E.164 format |
400 | INVALID_EMAIL_FORMAT | Invalid email address |
400 | MISSING_IDENTIFIER | No phone or email provided |
401 | INVALID_API_KEY | Missing or invalid API key |
401 | API_KEY_EXPIRED | API key has expired |
404 | CONTACT_NOT_FOUND | Contact does not exist |
404 | CAMPAIGN_NOT_FOUND | Campaign does not exist |
500 | INTERNAL_ERROR | Server error |
400 | MISSING_PHONE_NUMBER | Contact has no phone number |
403 | PLAN_REQUIRED | Feature requires a paid plan |
429 | SMS_LIMIT_EXCEEDED | SMS quota exhausted |
Webhooks
Receive real-time HTTP notifications when contacts, campaigns, or messages change. Create webhook endpoints programmatically or via the Dashboard.
Event Types
Event | Triggered when |
|---|---|
contact.created | A new contact is added |
contact.updated | A contact's details change |
contact.deleted | A contact is removed |
campaign.created | A new campaign is created |
campaign.enrolled | A contact is enrolled in a campaign |
message.sent | An SMS is sent successfully |
message.failed | An SMS delivery fails |
Webhook Payload
Every webhook delivery sends a JSON envelope:
{
"id": "delivery-uuid",
"type": "contact.created",
"created_at": "2026-03-25T10:30:00Z",
"api_version": "2026-03-25",
"data": {
"id": "contact-uuid",
"first_name": "John",
"last_name": "Smith",
"phone_e164": "+447912345678",
"email": null,
"marketing_consent": true,
"next_due_at": "2026-04-15T00:00:00Z",
"tags": [],
"created_at": "2026-03-25T10:30:00Z",
"updated_at": "2026-03-25T10:30:00Z"
}
}Signature Verification
Each delivery includes an X-Remindlo-Signature header for verifying authenticity:
X-Remindlo-Signature: t=1711360200,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8f9To verify, compute HMAC-SHA256 of {timestamp}.{raw_body} using your signing secret and compare with the v1 value.
const crypto = require('crypto');
function verifySignature(payload, header, secret) {
const [tPart, vPart] = header.split(',');
const timestamp = tPart.split('=')[1];
const signature = vPart.split('=')[1];
const expected = crypto
.createHmac('sha256', secret)
.update(timestamp + '.' + payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expected, 'hex')
);
}GET /v1/webhooks
List all webhook endpoints for your account.
curl -X GET "https://api.remindlo.co.uk/v1/webhooks" \
-H "x-api-key: sk_live_xxx"Response:
{
"success": true,
"endpoints": [
{
"id": "endpoint-uuid",
"name": "My CRM sync",
"target_url": "https://example.com/webhook",
"source": "manual",
"status": "active",
"event_types": ["contact.created", "contact.updated"],
"created_at": "2026-03-25T10:00:00Z",
"updated_at": "2026-03-25T10:00:00Z"
}
]
}POST /v1/webhooks
Create a new webhook endpoint. Returns a signing secret that is shown only once - store it securely.
Request Body
Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | A label for this endpoint |
target_url | string | Yes | HTTPS URL to receive events |
event_types | string[] | Yes | Events to subscribe to (see table above) |
curl -X POST "https://api.remindlo.co.uk/v1/webhooks" \
-H "x-api-key: sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"name": "My CRM sync",
"target_url": "https://example.com/webhook",
"event_types": ["contact.created", "contact.updated", "contact.deleted"]
}'Response:
{
"success": true,
"endpoint": {
"id": "endpoint-uuid",
"name": "My CRM sync",
"target_url": "https://example.com/webhook",
"source": "manual",
"status": "active",
"event_types": ["contact.created", "contact.updated", "contact.deleted"]
},
"signing_secret": "whsec_a1b2c3d4e5f6..."
}Important: The signing_secret is returned only once at creation time. Store it securely to verify incoming webhook signatures.
POST /v1/webhooks/delete
Delete a webhook endpoint and all its delivery history.
Request Body
Field | Type | Required | Description |
|---|---|---|---|
endpoint_id | string | Yes | The endpoint UUID to delete |
curl -X POST "https://api.remindlo.co.uk/v1/webhooks/delete" \
-H "x-api-key: sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{"endpoint_id": "endpoint-uuid"}'Response:
{
"success": true,
"deleted": true
}Retry Behaviour
Failed deliveries are retried automatically with exponential backoff: 1 min, 5 min, 30 min, 2 hours, 6 hours, 12 hours, 24 hours (up to 8 attempts total). Endpoints with repeated permanent failures are automatically disabled. You can monitor delivery status and replay failed deliveries from the Webhooks dashboard.
Use Cases
AI Agent Integration
AI agents (Claude, ChatGPT, custom agents) can manage your entire reminder workflow programmatically - adding contacts, enrolling in campaigns, and setting up webhooks to receive status updates.
User: "Add John Smith, phone 07912345678, to the birthday campaign
and set up a webhook so my CRM gets notified"
AI: [calls GET /v1/campaigns to find "Birthday" campaign]
AI: [calls POST /v1/contacts with campaign_ids]
AI: [calls POST /v1/webhooks to create endpoint for contact events]
AI: "Done! John is enrolled and your CRM will receive webhook
notifications for any contact changes."CRM Integration
Sync contacts from your CRM system automatically when appointments are booked. Use webhooks to push changes back to your CRM in real time. More use cases for using webhooks in a dedicated documentation.
Zapier / Make / n8n
Connect Remindlo to 8,000+ apps. Use our native Zapier integration or create webhook endpoints for Make and n8n.
Code Examples
JavaScript / Node.js
const response = await fetch('https://api.remindlo.co.uk/v1/contacts', {
method: 'POST',
headers: {
'x-api-key': 'sk_live_xxx',
'Content-Type': 'application/json'
},
body: JSON.stringify({
phone: '+447912345678',
first_name: 'John',
marketing_consent: true
})
});
const data = await response.json();
console.log(data.contact_id);Python
import requests
response = requests.post(
'https://api.remindlo.co.uk/v1/contacts',
headers={'x-api-key': 'sk_live_xxx'},
json={
'phone': '+447912345678',
'first_name': 'John',
'marketing_consent': True
}
)
print(response.json()['contact_id'])PHP
$ch = curl_init('https://api.remindlo.co.uk/v1/contacts');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'x-api-key: sk_live_xxx',
'Content-Type: application/json'
],
CURLOPT_POSTFIELDS => json_encode([
'phone' => '+447912345678',
'first_name' => 'John',
'marketing_consent' => true
])
]);
$response = json_decode(curl_exec($ch), true);
echo $response['contact_id'];Webhook Handler (Node.js / Express)
const express = require('express');
const crypto = require('crypto');
const app = express();
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-remindlo-signature'];
const [tPart, vPart] = signature.split(',');
const timestamp = tPart.split('=')[1];
const sig = vPart.split('=')[1];
const expected = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(timestamp + '.' + req.body)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(sig, 'hex'), Buffer.from(expected, 'hex'))) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
console.log(event.type, event.data);
// Handle event...
res.status(200).send('OK');
});Need Help?
Contact us at support@remindlo.co.uk for API support.