# SendHeron REST API

Base URL: `https://api.sendheron.com/api/v1`

## Authentication

Every request carries a bearer token:

```http
Authorization: Bearer <YOUR_API_KEY>
```

API keys are scoped. A key only reaches endpoints whose required scopes it holds, so a key that reads contacts cannot send email unless you grant it that too.

Available scopes: `contacts:read`, `contacts:write`, `sequences:read`, `sequences:write`, `templates:read`, `templates:write`, `analytics:read`, `emails:send`.

## Rate limits

All of these windows apply simultaneously: 60 per minute, 150 per 5 minutes, 300 per hour, 2000 per day. The tightest window you breach is the one that rejects the request.

## Endpoints (35)

### Contacts

Create, find and update the people you send to, plus their tags, events and engagement history.

#### GET /contacts

List contacts with pagination and filtering.

```http
GET https://api.sendheron.com/api/v1/contacts
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `contacts:read`

**Success status:** `200`

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `page` | integer | no | Zero-indexed page number. Defaults to `0`. |
| `limit` | integer | no | Items per page. |
| `term` | string | no | Free-text search across email and name. |
| `status` | string | no | Filter by contact status. |


#### POST /contacts

Create a new contact.

```http
POST https://api.sendheron.com/api/v1/contacts
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `contacts:write`

**Success status:** `201`

**Body parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `email` | string | yes | Contact email address. |
| `firstName` | string | no | First name. |
| `lastName` | string | no | Last name. |
| `properties` | object | no | Arbitrary custom fields, stored as JSONB. |
| `status` | string | no | Initial contact status. |
| `source` | string | no | Where the contact came from. |
| `tagIds` | uuid[] | no | Tags to attach by id. |
| `tagNames` | string[] | no | Tags to attach by name. Names that do not exist yet are created. |

> Use `PUT /contacts` instead if the contact may already exist. `POST` fails on a duplicate email.


#### PUT /contacts

Upsert a contact by email: create it, or merge the payload into the existing record.

```http
PUT https://api.sendheron.com/api/v1/contacts
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `contacts:write`

**Success status:** `200`

**Body parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `email` | string | yes | Match key for the upsert. |
| `firstName` | string | no | First name. |
| `lastName` | string | no | Last name. |
| `properties` | object | no | Custom fields, merged into any existing properties. |
| `tagNames` | string[] | no | Tags to attach by name. |

> This is the endpoint to reach for from a signup form or a sync job. It is idempotent on email.


#### GET /contacts/{id}

Get a single contact by ID.

```http
GET https://api.sendheron.com/api/v1/contacts/{id}
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `contacts:read`

**Success status:** `200`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | Contact UUID. |


#### PATCH /contacts/{id}

Update a contact by ID. Only the fields you send are changed.

```http
PATCH https://api.sendheron.com/api/v1/contacts/{id}
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `contacts:write`

**Success status:** `200`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | Contact UUID. |


#### DELETE /contacts/{id}

Delete a contact by ID.

```http
DELETE https://api.sendheron.com/api/v1/contacts/{id}
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `contacts:write`

**Success status:** `200`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | Contact UUID. |


#### GET /contacts/{id}/timeline

Get the engagement timeline for a contact.

```http
GET https://api.sendheron.com/api/v1/contacts/{id}/timeline
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `contacts:read`

**Success status:** `200`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | Contact UUID. |

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `page` | integer | no | Zero-indexed page number. Defaults to `0`. |
| `limit` | integer | no | Items per page. |


#### GET /contacts/{id}/emails

Get email activity for a contact.

```http
GET https://api.sendheron.com/api/v1/contacts/{id}/emails
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `contacts:read`

**Success status:** `200`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | Contact UUID. |

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `page` | integer | no | Zero-indexed page number. Defaults to `0`. |
| `limit` | integer | no | Items per page. |
| `status` | string | no | Filter by send status. |


#### POST /contacts/{id}/tags

Add tags to a contact, by id or by name.

```http
POST https://api.sendheron.com/api/v1/contacts/{id}/tags
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `contacts:write`

**Success status:** `201`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | Contact UUID. |

**Body parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `tagIds` | uuid[] | no | Tags to attach by id. |
| `tagNames` | string[] | no | Tags to attach by name. |

> Adding a tag is what starts a tag-triggered sequence, so this doubles as the enrollment trigger.


#### DELETE /contacts/{id}/tags/{tagId}

Remove a tag from a contact.

```http
DELETE https://api.sendheron.com/api/v1/contacts/{id}/tags/{tagId}
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `contacts:write`

**Success status:** `200`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | Contact UUID. |
| `tagId` | uuid | yes | Tag UUID to remove. |


#### POST /contacts/{id}/events

Track a custom event for a contact.

```http
POST https://api.sendheron.com/api/v1/contacts/{id}/events
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `contacts:write`

**Success status:** `201`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | Contact UUID. |

**Body parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | yes | Event name. |
| `properties` | object | no | Event-specific metadata, stored as JSONB. |


#### POST /contacts/{id}/sequences/{seqId}/enroll

Enroll a contact in a sequence directly, without waiting for a trigger.

```http
POST https://api.sendheron.com/api/v1/contacts/{id}/sequences/{seqId}/enroll
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `contacts:write`, `sequences:write`

**Success status:** `201`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | Contact UUID. |
| `seqId` | uuid | yes | Sequence UUID. |


#### DELETE /contacts/{id}/sequences/{seqId}/unenroll

Unenroll a contact from a sequence.

```http
DELETE https://api.sendheron.com/api/v1/contacts/{id}/sequences/{seqId}/unenroll
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `contacts:write`, `sequences:write`

**Success status:** `200`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | Contact UUID. |
| `seqId` | uuid | yes | Sequence UUID. |


### Tags

The labels that drive segmentation and sequence entry.

#### GET /tags

List tags with pagination and search.

```http
GET https://api.sendheron.com/api/v1/tags
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `contacts:read`

**Success status:** `200`

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `page` | integer | no | Zero-indexed page number. Defaults to `0`. |
| `limit` | integer | no | Items per page. |


#### POST /tags

Create a new tag.

```http
POST https://api.sendheron.com/api/v1/tags
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `contacts:write`

**Success status:** `201`


### Sequences

Read automation definitions, their steps, and how enrolled contacts are moving through them.

#### GET /sequences

List sequences with pagination and filtering.

```http
GET https://api.sendheron.com/api/v1/sequences
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `sequences:read`

**Success status:** `200`

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `page` | integer | no | Zero-indexed page number. Defaults to `0`. |
| `limit` | integer | no | Items per page. |


#### GET /sequences/{id}

Get a single sequence by ID.

```http
GET https://api.sendheron.com/api/v1/sequences/{id}
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `sequences:read`

**Success status:** `200`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | Sequence UUID. |


#### GET /sequences/{id}/steps

Get the steps and edges for a sequence. This is the graph the visual builder renders.

```http
GET https://api.sendheron.com/api/v1/sequences/{id}/steps
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `sequences:read`

**Success status:** `200`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | Sequence UUID. |


#### GET /sequences/{id}/stats

Get enrollment stats for a sequence.

```http
GET https://api.sendheron.com/api/v1/sequences/{id}/stats
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `sequences:read`

**Success status:** `200`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | Sequence UUID. |


#### GET /sequences/{id}/step-stats

Get per-step analytics for a sequence: sends, opens and clicks at each node.

```http
GET https://api.sendheron.com/api/v1/sequences/{id}/step-stats
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `sequences:read`

**Success status:** `200`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | Sequence UUID. |


#### GET /sequences/{id}/enrollments

List enrollments for a sequence, paginated.

```http
GET https://api.sendheron.com/api/v1/sequences/{id}/enrollments
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `sequences:read`

**Success status:** `200`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | Sequence UUID. |

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `page` | integer | no | Zero-indexed page number. Defaults to `0`. |
| `limit` | integer | no | Items per page. |


#### POST /sequences/{id}/enrollments/bulk

Bulk enroll contacts in a sequence.

```http
POST https://api.sendheron.com/api/v1/sequences/{id}/enrollments/bulk
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `sequences:write`

**Success status:** `201`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | Sequence UUID. |


#### DELETE /sequences/{id}/enrollments/bulk

Bulk cancel enrollments for contacts in a sequence.

```http
DELETE https://api.sendheron.com/api/v1/sequences/{id}/enrollments/bulk
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `sequences:write`

**Success status:** `200`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | Sequence UUID. |


### Enrollments

Pause and resume a single contact's run through a sequence.

#### PATCH /enrollments/{id}/pause

Pause an active enrollment. The contact stops advancing but keeps its position.

```http
PATCH https://api.sendheron.com/api/v1/enrollments/{id}/pause
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `sequences:write`

**Success status:** `200`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | Enrollment UUID. |


#### PATCH /enrollments/{id}/resume

Resume a paused enrollment from where it stopped.

```http
PATCH https://api.sendheron.com/api/v1/enrollments/{id}/resume
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `sequences:write`

**Success status:** `200`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | Enrollment UUID. |


### Templates

Reusable email bodies with variable substitution.

#### GET /templates

List templates with pagination and filtering.

```http
GET https://api.sendheron.com/api/v1/templates
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `templates:read`

**Success status:** `200`

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `page` | integer | no | Zero-indexed page number. Defaults to `0`. |
| `limit` | integer | no | Items per page. |


#### GET /templates/{id}

Get a single template by ID.

```http
GET https://api.sendheron.com/api/v1/templates/{id}
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `templates:read`

**Success status:** `200`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | Template UUID. |


#### POST /templates

Create a new template.

```http
POST https://api.sendheron.com/api/v1/templates
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `templates:write`

**Success status:** `201`


#### PATCH /templates/{id}

Update an existing template.

```http
PATCH https://api.sendheron.com/api/v1/templates/{id}
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `templates:write`

**Success status:** `200`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | Template UUID. |


#### DELETE /templates/{id}

Delete a template.

```http
DELETE https://api.sendheron.com/api/v1/templates/{id}
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `templates:write`

**Success status:** `200`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | Template UUID. |


### Emails

Send transactional, templated and bulk email directly.

#### POST /emails/send

Send a single transactional email.

```http
POST https://api.sendheron.com/api/v1/emails/send
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `emails:send`

**Success status:** `201`


#### POST /emails/send-template

Send an email using a template with dynamic variables.

```http
POST https://api.sendheron.com/api/v1/emails/send-template
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `emails:send`

**Success status:** `201`


#### POST /emails/send-bulk

Send bulk email to a list or a set of contacts.

```http
POST https://api.sendheron.com/api/v1/emails/send-bulk
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `emails:send`

**Success status:** `201`

> Bulk sends are permission-based only. See the anti-spam policy before using this endpoint.


### Analytics

Aggregate delivery and engagement figures.

#### GET /analytics/dashboard

Get the aggregate dashboard figures for the workspace.

```http
GET https://api.sendheron.com/api/v1/analytics/dashboard
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `analytics:read`

**Success status:** `200`


#### GET /analytics/campaigns/{id}

Get analytics for a specific campaign.

```http
GET https://api.sendheron.com/api/v1/analytics/campaigns/{id}
Authorization: Bearer <YOUR_API_KEY>
```

**Required scopes:** `analytics:read`

**Success status:** `200`

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | Campaign UUID. |

