---
title: "SMS Reminder API Documentation"
slug: "sms-reminder-api"
type: "help-article"
canonical: "https://www.remindlo.co.uk/help/sms-reminder-api"
category: "Developer"
read_time: "10 min read"
published_at: "2026-01-30T14:29:00+00:00"
updated_at: "2026-09-14T08:33:20.423682+00:00"
author: "Peter Bednarek"
keywords: ["sms api", "reminder api", "appointment reminder api", "crm integration", "sms automation", "contact api"]
---

# 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:

1.  **Get your API key** from [Dashboard → Settings → API Keys](/dashboard/settings)
    
2.  **List your campaigns** to get campaign IDs
    
3.  **Add contacts** and enroll them in campaigns
    

Optionally, set up **webhooks** to receive real-time notifications when data changes.

```bash
# 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_here
```

**Important:** Keep your API key secure. Never expose it in client-side code or public repositories.

## Base URL

```
https://api.remindlo.co.uk/v1
```

A machine-readable OpenAPI 3.1 description of every endpoint below is published at [openapi.json](https://www.remindlo.co.uk/openapi.json).

## Endpoints

### GET /v1/campaigns

List all available SMS campaigns for your account.

```bash
curl -X GET "https://api.remindlo.co.uk/v1/campaigns" \
  -H "x-api-key: sk_live_xxx"
```

**Response:**

```json
{
  "success": true,
  "campaigns": [
    {
      "id": "uuid",
      "name": "Appointment Reminder",
      "type": "recurring",
      "channels": ["sms"],
      "status": "running",
      "auto_enroll": true,
      "created_at": "2026-03-25T10:00:00Z"
    }
  ]
}
```

### 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) |
| email | 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 |  | `days`, `months`, or `years`. Required when is\_recurrent is true |

**\* At least one of phone or email is required.**

**A note on consent:** setting `marketing_consent` to `false` is durable. Calendar sync never raises it back to `true`, so an opt-out recorded through the API stays recorded. Setting it to `true` through the API is always honoured, so a customer who later opts back in can be re-enrolled the same way.

```bash
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": "2026-04-15T00:00:00Z",
    "campaign_ids": ["campaign-uuid"],
    "is_recurrent": true,
    "recurrent_interval_value": 12,
    "recurrent_interval_unit": "months"
  }'
```

**Response:**

```json
{
  "success": true,
  "contact_id": "uuid",
  "action": "created",
  "contact": {
    "id": "uuid",
    "first_name": "John",
    "last_name": "Smith",
    "phone": "+447912345678",
    "marketing_consent": true,
    "next_due_at": "2026-04-15T00:00:00Z",
    "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.**

```bash
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:**

```json
{
  "enrollment_id": "uuid",
  "status": "enrolled"
}
```

**Possible status values:**

-   `enrolled` — contact successfully enrolled
    
-   `already_enrolled` — contact was already in this campaign
    
-   `skipped` — 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: `sms` |

```bash
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):**

```json
{
  "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 plan
    
-   `404 CONTACT_NOT_FOUND` — contact does not exist or has no phone number
    
-   `429 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) |

```bash
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.

```bash
# 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:

```json
{
  "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](/dashboard/webhooks).

### 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:

```json
{
  "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=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8f9
```

To verify, compute HMAC-SHA256 of `{timestamp}.{raw_body}` using your signing secret and compare with the `v1` value.

```javascript
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.

```bash
curl -X GET "https://api.remindlo.co.uk/v1/webhooks" \
  -H "x-api-key: sk_live_xxx"
```

**Response:**

```json
{
  "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) |

```bash
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:**

```json
{
  "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 |

```bash
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:**

```json
{
  "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](/dashboard/webhooks).

## 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](https://www.remindlo.co.uk/help/webhooks-real-time-notifications).

### Zapier / Make / n8n

Connect Remindlo to 9,000+ apps. Use our [native Zapier integration](/dashboard/integrations/zapier) or create webhook endpoints for Make and n8n.

## Code Examples

### JavaScript / Node.js

```javascript
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

```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

```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)

```javascript
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](mailto:support@remindlo.co.uk) for API support.

---

Canonical URL: https://www.remindlo.co.uk/help/sms-reminder-api
