---
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-04-15T19:22:48.977026+00:00"
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
```

## 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
{
  "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)

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.**

```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": "2025-03-15",
    "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": "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.**

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