# Bulk Templates

https://dev.wisecp.com/es/bulk-templates

The six endpoints that manage reusable bulk notification campaigns.

## Overview

A bulk template is a campaign **saved for reuse**: the message, the subject, the channel and the audience filters kept together. Instead of rebuilding the same announcement each time, you write it once and store it.

A template **sends nothing** on its own. Sending happens on a separate endpoint, and sending one on a regular basis is what scheduled tasks are for.

## Reference

### Listing the Templates

get/api/v1/admin/tools/bulk/templates

`Tools/GetBulkTemplates` admin paged

Returns the saved bulk notification campaigns.

Query parameters 4

template_typestringFilters by channel.

pageintDefaults to 1.

limitintDefaults to 25, maximum 100.

searchstringSearches the templates.

Response fields data[] — 10

idintId of the template.

template_namestringThe template name. For you only; the client never sees it.

template_typestringThe channel: `mail` or `sms`.

typestringThe audience: `member` for clients, `staff` for staff.

subjectstringThe message subject.

submission_typestringThe envelope: `single` one each, `multiple` all together.

newsletterstringThe newsletter key. When filled, the newsletter list is used instead of the filters.

created_atstring | nullWhen it was created.

updated_atstring | nullWhen it last changed.

last_sentstring | nullWhen it was last sent.

Errors 1

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -G 'https://panel.example.com/api/v1/admin/tools/bulk/templates' \
  -H "Authorization: Bearer $API_KEY" \
  -d template_type=mail
```

```javascript
const url = new URL('https://panel.example.com/api/v1/admin/tools/bulk/templates');
url.searchParams.set('template_type', 'mail');

const res  = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
const body = await res.json();
```

```php
$url = 'https://panel.example.com/api/v1/admin/tools/bulk/templates?' . http_build_query(['template_type' => 'mail']);

$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// The list carries neither the message body nor the filters; those come on the detail only.
$templates = Api::Tools()->GetBulkTemplates([], ['template_type' => 'mail'])['data'];
```

### Template Detail

get/api/v1/admin/tools/bulk/templates/{id}

`Tools/GetBulkTemplate` admin the filters are here

Returns a campaign with its message and its audience filters.

Response fields data — 24

idintId of the template.

template_namestringThe template name. For you only; the client never sees it.

template_typestringThe channel: `mail` or `sms`.

typestringThe audience: `member` for clients, `staff` for staff.

subjectstringThe message subject.

submission_typestringThe envelope: `single` one each, `multiple` all together.

newsletterstringThe newsletter key. When filled, the newsletter list is used instead of the filters.

created_atstring | nullWhen it was created.

updated_atstring | nullWhen it last changed.

last_sentstring | nullWhen it was last sent.

messagestringThe message body.

ccstringAddresses to copy in.

without_productsintTargets clients with no product at all.

birthday_marketingintTargets clients whose birthday it is.

auto_submissionintWhether the template is sent on its own by a scheduled task.

user_groupsarrayThe client group filter.

departmentsarrayThe department filter.

countriesarrayThe country filter.

languagesarrayThe language filter.

servicesarrayThe product filter.

serversarrayThe server filter.

addonsarrayThe add-on filter.

services_statusarrayThe service status filter.

client_statusarrayThe client status filter.

Errors 3

invalid_id422The id is not valid.

not_found404No such template.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl 'https://panel.example.com/api/v1/admin/tools/bulk/templates/5' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
const res  = await fetch('https://panel.example.com/api/v1/admin/tools/bulk/templates/5', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/tools/bulk/templates/5');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// To see how many a template would reach, hand its filters to the counting endpoint.
$tpl   = Api::Tools()->GetBulkTemplate(['id' => 5])['data'];
$count = Api::Tools()->GetBulkContacts([
    'user_type'   => $tpl['type'] === 'staff' ? 'staff' : 'client',
    'user_groups' => $tpl['user_groups'],
    'countries'   => $tpl['countries'],
])['data']['count'];
```

### Creating a Template

post/api/v1/admin/tools/bulk/templates

`Tools/CreateBulkTemplate` admin 201

Saves a campaign to reuse. Saving sends nothing.

Body 11

template_namestringrequiredThe template name.

messagestringrequiredThe message body.

subjectstringThe message subject. Required on e-mail.

template_typestringThe channel. It defaults to e-mail.

typestringThe audience. Whatever you send for clients is stored as `member`.

submission_typestringThe envelope form.

newsletterstringThe newsletter key.

ccstringAddresses to copy in.

without_productsintTargets clients with no product at all.

birthday_marketingintTargets clients whose birthday it is.

*arrayThe nine filter arrays: groups, departments, countries, languages, products, servers, add-ons, service status and client status.

Response fields data — 24

dataobjectThe campaign saved, with the filter arrays already decoded. Same shape as the detail endpoint. To send it, hand the new id to the submit endpoint or give it a schedule.

Errors 4

template_name_required422The template name was empty.

subject_required422The subject was empty on an e-mail.

body_required422The message body was empty.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X POST 'https://panel.example.com/api/v1/admin/tools/bulk/templates' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"template_name":"Welcome Campaign","template_type":"mail","type":"member","subject":"Welcome","message":"Hello!","user_groups":[1]}'
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/tools/bulk/templates', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    template_name: 'Welcome Campaign',
    template_type: 'mail',
    type: 'member',
    subject: 'Welcome',
    message: 'Hello!',
    user_groups: [1],
  }),
});

const body = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/tools/bulk/templates');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'template_name' => 'Welcome Campaign',
        'subject'       => 'Welcome',
        'message'       => $html,
        'user_groups'   => [1],
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// Saving a template SENDS NOTHING: call the bulk submit endpoint separately to send it.
$tpl = Api::Tools()->CreateBulkTemplate([
    'template_name' => 'Welcome Campaign',
    'subject'       => 'Welcome',
    'message'       => $html,
    'user_groups'   => [1],
])['data'];
```

### Updating a Template

patch/api/v1/admin/tools/bulk/templates/{id}

`Tools/UpdateBulkTemplate` admin

Applies the fields you send; the name, the message and the subject cannot be emptied.

Body 11

template_namestringrequiredThe template name.

messagestringrequiredThe message body.

subjectstringThe message subject. Required on e-mail.

template_typestringThe channel. It defaults to e-mail.

typestringThe audience. Whatever you send for clients is stored as `member`.

submission_typestringThe envelope form.

newsletterstringThe newsletter key.

ccstringAddresses to copy in.

without_productsintTargets clients with no product at all.

birthday_marketingintTargets clients whose birthday it is.

*arrayThe nine filter arrays: groups, departments, countries, languages, products, servers, add-ons, service status and client status.

Response fields data — 24

dataobjectThe campaign as it now stands, with the filter arrays already decoded. Same shape as the detail endpoint.

Errors 5

not_found404No such template.

template_name_required422The name you sent was empty.

subject_required422The subject you sent was empty.

body_required422The message you sent was empty.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X PATCH 'https://panel.example.com/api/v1/admin/tools/bulk/templates/5' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"subject":"Welcome (updated)","message":"Hi there!"}'
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/tools/bulk/templates/5', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    subject: 'Welcome (updated)',
    message: 'Hi there!',
  }),
});

const body = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/tools/bulk/templates/5');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PATCH',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'subject' => 'Welcome (updated)',
        'message' => $html,
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// If a scheduled task points at this template, the change takes effect on its NEXT run.
Api::Tools()->UpdateBulkTemplate([
    'id'      => 5,
    'subject' => 'Welcome (updated)',
]);
```

### Deleting a Template

delete/api/v1/admin/tools/bulk/templates/{id}

`Tools/DeleteBulkTemplate` admin its records go too

Deletes the campaign and the send records belonging to it.

Response fields data — 2

deletedboolWhether the delete succeeded.

idintId of the deleted template.

Errors 2

not_found404No such template.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X DELETE 'https://panel.example.com/api/v1/admin/tools/bulk/templates/5' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/tools/bulk/templates/5', {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/tools/bulk/templates/5');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// The delete also removes the campaign's SEND HISTORY; when it went and to whom is lost.
Api::Tools()->DeleteBulkTemplate(['id' => 5]);
```

### Deleting in Bulk

post/api/v1/admin/tools/bulk/templates/bulk-delete

`Tools/BulkDeleteBulkTemplates` admin the panel asks for a password

Deletes several campaigns.

Body 1

idsint[]requiredIds of the templates to delete.

Response fields data — 2

deletedboolWhether the delete ran.

countintHow many templates were deleted.

Errors 2

ids_required422No id was given.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X POST 'https://panel.example.com/api/v1/admin/tools/bulk/templates/bulk-delete' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"ids":[5,6,7]}'
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/tools/bulk/templates/bulk-delete', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ ids: [5, 6, 7] }),
});

const body = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/tools/bulk/templates/bulk-delete');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['ids' => [5, 6, 7]]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// The panel asks for the admin password here; on the API the key's scope is enough.
Api::Tools()->BulkDeleteBulkTemplates(['ids' => [5, 6, 7]]);
```

## Pitfalls

> **Saving is not sending**
> 
> Creating or updating a template sends no message; it only writes the record. Sending the campaign means calling the bulk submit endpoint separately, and sending it regularly means defining a scheduled task.

> **The filters are not in the list**
> 
> The list endpoint **does not return** the message body or the nine filter arrays; those come only on a single template's detail. Seeing who a campaign would reach means reading the detail and handing its filters to the counting endpoint.

> **Deleting takes the send history too**
> 
> Deleting a template removes not only the campaign but the **send records** that belong to it. Which announcement went when, and to whom, is lost. If you are merely done with a campaign, consider leaving it in place rather than deleting it.

> **A change takes effect on the next run**
> 
> When a scheduled task points at a template, updating it sends nothing at that moment; the new text is used on the **next** run. Correcting a wrong text does nothing about the sends that already went out.

> **The panel asks for a password, the API for a scope**
> 
> The bulk delete asks for the admin password in the panel. The API has no such second step: the key's scope is enough. Hand out a key carrying this scope knowing exactly who holds it.

## Related Articles

- [Bulk Messaging](https://dev.wisecp.com/en/bulk-messaging)
- [Scheduled Bulk Tasks](https://dev.wisecp.com/en/scheduled-bulk-tasks)
