Private Batch Events extend Private Events by allowing publishers to send multiple subscriber-specific events in a single API call. Each event appears exclusively in the target subscriber’s calendar, enabling efficient bulk personalization (e.g., tailored reminders, bookings).
Private Events send a single private event to a single subscriber. Private Batch Events allow you to send multiple private events in one request, either to multiple different subscribers (each event going only to its specific subscriber) or to a single subscriber receiving multiple private events.
Use Batch Events for subscriber-specific content; use standard Event API for broadcast events.
A subscriber’s ecal_id is required for many actions, including retrieving Private Events. To ensure you capture this, follow the steps below:
It’s important to store the ecal_id immediately after subscriber creation to avoid repeated API calls. It’s a permanent identifier for the subscriber.
You can retrieve a Subscriber’s Private Events by performing an HTTP GET /batch/events request, providing the Subscriber’s ecal_id as a parameter.
To retrieve a Subscriber’s Private Events, you must specify the type=private.
Example Request
GET /apiv2/batch/events?type=private&ecal_id=<subscriber-ecal-id>&apiKey=<api-key>&apiSign=<api-sign>
Example Response
{
"data": [
{
"id": "6942008c26465d00029e4470",
"type": "private",
"name": "(Custom 1 events) Batch Event 1 future live",
"publisherId": 14566,
"publisherOrgId": 6477,
"twitterTag": "",
"location": "ECAL Melbourne HQ - future live",
"startDate": "2026-11-01",
"startTime": "09:00",
"endDate": "2026-11-01",
"endTime": "09:15",
"allDay": false,
"timezone": "Australia/Melbourne",
"alert": "15M",
"alert2": "1H",
"details": "Custom event details for Batch Event 1.",
"draft": 0,
"quickLink[1][name]": "",
"quickLink[1][url]": "",
"quickLink[1][original_url]": "",
"quickLink[10][name]": "",
"quickLink[10][url]": "",
"quickLink[10][original_url]": "",
"shortUrl": "",
"social": {},
"featured": 0,
"tags": [],
"reference": "ref-6941f4a34e0e2e0002af4f99- future live",
"event_banner": "",
"focus-x": "",
"focus-y": "",
"primary_link": 1,
"secondary_link": 2,
"sponsored_message": "",
"referenceType": "referenceType-ecalIds-future live",
"startGmt": "20261031T220000Z",
"endGmt": "20261031T221500Z",
"created": "2025-12-17T00:59:56.074Z",
"modified": "2025-12-17T00:59:56.074Z",
"startDateFull": "2026-11-01T09:00:00+11:00",
"due": "",
"endDateFull": "2026-11-01T09:15:00+11:00",
"calendarId": "6941f4a49c4e6e0002961c70"
},
{
"id": "6941fd2026465d00029e446b",
"type": "private",
"name": "(Custom 1 events) Batch Event 1 draft event",
"publisherId": 14566,
"publisherOrgId": 6477,
"twitterTag": "",
"location": "ECAL Melbourne HQ - draft event",
"startDate": "2026-11-01",
"startTime": "09:00",
"endDate": "2026-11-01",
"endTime": "09:15",
"allDay": false,
"timezone": "Australia/Melbourne",
"alert": "15M",
"alert2": "1H",
"details": "Custom event details for Batch Event 1.draft event",
"draft": 0,
"quickLink[1][name]": "",
"quickLink[1][url]": "",
"quickLink[1][original_url]": "",
"quickLink[10][name]": "",
"quickLink[10][url]": "",
"quickLink[10][original_url]": "",
"shortUrl": "",
"social": {},
"featured": 0,
"tags": [],
"reference": "ref-6941f4a34e0e2e0002af4f99-draft event",
"event_banner": "",
"focus-x": "",
"focus-y": "",
"primary_link": 1,
"secondary_link": 2,
"sponsored_message": "",
"referenceType": "referenceType-ecalIds-draft event",
"startGmt": "20261031T220000Z",
"endGmt": "20261031T221500Z",
"created": "2025-12-17T00:45:20.56Z",
"modified": "2025-12-17T00:45:20.56Z",
"startDateFull": "2026-11-01T09:00:00+11:00",
"due": "",
"endDateFull": "2026-11-01T09:15:00+11:00",
"calendarId": "6941f4a49c4e6e0002961c70"
}
],
"status": "200",
"statusLong": "OK"
}
You can use the following parameters to further restrict and refine your retrieval.
| Parameter | Required | Description |
|---|---|---|
| apiKey | YES | The apiKey provided by ECAL and is found in Publisher Admin Portal |
| apiSign | YES | MD5 string of all parameters signed by the SECRET |
| type | YES | private |
| page | NO | The page number of the event list |
| limit | NO | Defaults to 100 and Maximum 100. Work in conjunction with page to create pagination |
When adding an event, you can aggregate all events sent to subscribers using the referenceType
field. This field is not visible to subscribers but provides a convenient way to retrieve events
using a single key. For example, you could aggregate all invoices sent in November under a common
referenceType.
Example Request
GET /apiv2/batch/events?type=private&referenceType=november-2025-invoices&apiKey=<api-key>&apiSign=<api-sign>
The response and available filters are identical to those detailed in Retrieving Events by Subscriber ID
For powerful, targeted queries, you can combine the subscriber identification (ecal_id) with the referenceType.
Example Request
GET /apiv2/batch/events?type=private&referenceType=<reference-type>&ecal_id=<ecal-id>&apiKey=<api-key>&apiSign=<api-sign>
The response and available filters are identical to those detailed in Retrieving Events by Subscriber ID
If you need to get the data for multiple events, you can initiate a POST request to private batch API to select specific ids.
Example Request
POST /apiv2/batch/events/search?type=private&apiKey=<api-key>&apiSign=<api-sign>' \
--header 'Content-Type: application/json' \
--data '{
"ids": [
"<event-id-1>",
"<event-id-2>",
...
"<event-id-n>"
]
}'
The response and available filters are identical to those detailed in Retrieving Events by Subscriber ID
Add events is the first step to send subscribers specific events and reminders. In Batch API, you can send up to 1000 records to add in a single request.
Example Request
POST /apiv2/batch/events?apiKey=<api-key>&apiSign=<api-sign>' \
--header 'Content-Type: application/json' \
--data '{
"type": "private",
"events": [
{
"ecalId": "632962b11c54decb4ed81e9e",
"name": "batch-event-1",
"reference": "batch-reference-1",
"referenceType": "batch-reference-type-1",
"location": "ACME Corp Payment Reminders",
"startDate": "2026-01-23",
"startTime": "09:00",
"endDate": "2026-01-23",
"endTime": "09:15",
"allDay": "no",
"timezone": "UTC",
"alert": "15M",
"details": "We haven'\''t received payment for Invoice #(45009).",
"draft": 0,
"sponsored_message": ""
},
{
"ecalId": "632962b11c54decb4ed81e9e",
"name": "batch-event-2",
"reference": "batch-reference-2",
"referenceType": "batch-reference-type-2",
"location": "location-batch-2",
"startDate": "2026-01-23",
"startTime": "09:00",
"endDate": "2026-01-23",
"endTime": "09:15",
"allDay": "no",
"timezone": "UTC",
"alert": "15M",
"details": "details-2",
"draft": 0,
"sponsored_message": ""
}
]
}'
Ensure that you set the type: private and draft: 0. If no draft parameter is provided, our API will assume this event is not ready to sync to a subscriber’s private calendar and will default to draft: 1.
When your request is successful, the API returns a 207-Multistatus response similar to the example
below. The response includes a few fields that should be considered in interpreting of the response.
| Parameter | Description |
|---|---|
| status | The status of the record. |
| id | The id of the inserted event |
| scheduleId | The scheduleId of the event |
| reference | The reference of the processed record |
Store the id and scheduleId returned in the response, keyed by the reference value sent in
your request. These values are required for subsequent operations like event retrieval or deletion.
Example Response
{
"status": "207",
"statusLong": "Multi-Status",
"total_processed": 2,
"added": 2,
"items": [
{
"id": "694251b7a385c89557fdf7c4",
"scheduleId": "63296309eea1254cbf159d85",
"reference": "batch-reference-1",
"status": 201
},
{
"id": "694251b7a385c89557fdf7c5",
"scheduleId": "63296309eea1254cbf159d85",
"reference": "batch-reference-2",
"status": 201
}
],
"elapsed": "59.078667ms"
}
When adding reference and referenceType to the request body, please ensure that you don’t
include spaces in their values.
However, there are some edge cases that should be considered:
If any record in your request includes a reference that already exists in the other records, the API returns the following response and stops processing.
Example Request
POST /apiv2/batch/events?type=private&apiKey=<api-key>&apiSign=<api-sign>' \
--header 'Content-Type: application/json' \
--data '{
"type": "private",
"events": [
{
"ecalId": "632962b11c54decb4ed81e9e",
"name": "Your Payment is now overdue! 1",
"reference": "batch-1", ⬅
...
},
{
"ecalId": "632962b11c54decb4ed81e9e",
"name": "Your Payment is now overdue! 2",
"reference": "batch-1", ⬅
...
},
{
"ecalId": "632962b11c54decb4ed81e9e",
"name": "Your Payment is now overdue! 2",
"reference": "batch-2",
...
}
]
}'
Example Response
{
"status": "207",
"statusLong": "Multi-Status",
"total_processed": 1,
"added": 0,
"errors": 1,
"items": [
{
"reference": "batch-1", ⬅
"status": 400,
"error": "given parameter is not valid"
}
],
"elapsed": "236.292µs"
}
If any record in your request includes a reference that already exists in the database, the API ignores those duplicate records and continues adding the remaining valid records.
Example Request
POST /apiv2/batch/events?type=private&apiKey=<api-key>&apiSign=<api-sign>' \
--header 'Content-Type: application/json' \
--data '{
"type": "private",
"events": [
{
"ecalId": "632962b11c54decb4ed81e9e",
"name": "Your Payment is now overdue! 1",
"reference": "batch-reference-11",
...
},
{
"ecalId": "632962b11c54decb4ed81e9e",
"name": "Your Payment is now overdue! 2",
"reference": "5aaa02e58cbe07bb608b4568_private_", ⬅
...
}
]
}'
Example Response
{
"status": "207",
"statusLong": "Multi-Status",
"total_processed": 2,
"added": 1,
"errors": 1,
"items": [
{
"reference": "5aaa02e58cbe07bb608b4568_private_", ⬅
"status": 400,
"error": "one event with same reference already exists"
},
{
"id": "69425b23a385c8a0d73c9ab9",
"scheduleId": "63296309eea1254cbf159d85",
"reference": "batch-reference-11",
"status": 201
}
],
"elapsed": "27.122125ms"
}
If any record in your request targets an unsubscribed or inactive subscriber, that record is rejected, while the remaining valid records continue to be processed.
Example Request
POST /batch/events?apiKey=<api-key>&apiSign=<api-sign>' \
--header 'Content-Type: application/json' \
--data '{
"type": "private",
"events": [
{
"ecalId": "66e3ecca5b18a8946100e7f9",
"name": "batch-event-104",
"reference": "batch-reference-104",
...
},
{
"ecalId": "633642de86ee0a3ad8e18e49", ⬅
"name": "batch-event-105",
"reference": "batch-reference-105", ⬅
...
}
]
}'
Example Response
{
"status": "207",
"statusLong": "Multi-Status",
"total_processed": 2,
"added": 1,
"errors": 1,
"items": [
{
"id": "69425c31a385c8a0d73c9abe",
"scheduleId": "63296309eea1254cbf159d85",
"reference": "batch-reference-104",
"status": 201
},
{
"reference": "batch-reference-105", ⬅
"status": 400,
"error": "subscriber is not active"
}
],
"elapsed": "35.307167ms"
}
If any record in your request targets an invalid subscriber, that record is rejected, while the remaining valid records continue to be processed.
Example Request
POST /apiv2/batch/events?apiKey=<api-key>&apiSign=<api-sign>' \
--header 'Content-Type: application/json' \
--data '{
"type": "private",
"events": [
{
"ecalId": "632962b11c54decb4ed81e9e",
"name": "batch-event-1",
"reference": "batch-reference-1",
...
},
{
"ecalId": "632962b11c54decb4ed81101", ⬅
"name": "batch-event-3",
"reference": "batch-reference-3", ⬅
...
}
]
}'
Example Response
{
"status": "207",
"statusLong": "Multi-Status",
"total_processed": 2,
"added": 1,
"errors": 1,
"items": [
{
"reference": "batch-reference-3", ⬅
"status": 400,
"error": "subscriber id is not valid id" ⬅
},
{
"id": "69437209a385c864541a4ae0",
"scheduleId": "63296309eea1254cbf159d85",
"reference": "batch-reference-1",
"status": 201
}
],
"elapsed": "53.715917ms"
}
If any record in your request includes an invalid data type or value including the timezone, event type, empty body, incorrect structure, etc., the API returns an error and stops processing the remaining records like the one seen in number 1.
Use the Delete Events endpoint to remove scheduled events from subscribers’ calendars. The Batch Delete API supports deleting up to 1,000 event IDs in a single request for efficient bulk operations.
Example Request
DELETE /apiv2/batch/events?type=private&apiKey=<api-key>&apiSign=<api-sign> \
--header 'Content-Type: application/json' \
--data '{
"ids" :[
"69437209a385c864541a4ae0",
"69437209a385c864541a4ae1"
]
}'
A successful request returns a 207 Multi-Status response, indicating partial or mixed success
across
multiple operations. Review the response fields below to interpret individual operation outcomes.
| Parameter | Description |
|---|---|
| status | The status of the record. |
| id | The id of the deleted event. |
Example Response
{
"status": "207",
"statusLong": "Multi-Status",
"total_processed": 2,
"deleted": 2,
"not_found": 0,
"not_deleted": 0,
"errors": 0,
"items": [
{
"id": "69437209a385c864541a4ae0",
"status": 200
},
{
"id": "69437209a385c864541a4ae1",
"status": 200
}
],
"elapsed": "40.173708ms"
}
Like Adding Private Batch Events There are edge cases that should be considered when working with the DELETE API.
Empty body or invalid body structure cause the entire batch deletion to fail immediately. No partial processing occurs.
Example Request
DELETE /apiv2/batch/events?apiKey=<api-key>&apiSign=<api-sign> \
--header 'Content-Type: application/json' \
--data '{"ids":[]}'
Example Response
{
"status": "400",
"statusLong": "Validation failed.",
"errors": {
"ids": "at least one valid event should be sent"
}
}
Invalid event IDs cause the entire batch deletion to fail immediately. No partial processing occurs.
Example Request
DELETE /apiv2/batch/events?type=private&apiKey=<api-key>&apiSign=<api-sign> \
--header 'Content-Type: application/json' \
--data '{
"ids": [
"invalid-mongo-id",
"69437209a385c864541a4ae0"
]
}'
Example Response
{
"status": "400",
"statusLong": "Validation failed.",
"errors": {
"ids": "one or more event ids are invalid"
}
}
Duplicate event IDs in the batch payload are automatically removed before processing. The API continues deletion of unique valid IDs.
Example Request
DELETE /apiv2/batch/events?type=private&apiKey=<api-key>&apiSign=<api-sign> \
--header 'Content-Type: application/json' \
--data '{
"ids" :[
"6334f050509d435e77922eb9",
"6334f050509d435e77922eb9"
]
}'
Example Response
{
"status": "207",
"statusLong": "Multi-Status",
"total_processed": 1,
"deleted": 1,
"not_found": 0,
"not_deleted": 0,
"errors": 0,
"items": [
{
"id": "6334f050509d435e77922eb9",
"status": 200
}
],
"elapsed": "66.935125ms"
}
Non-existent event IDs in batch payloads are processed individually. Missing IDs return 404 status
per item without failing the entire batch.
Example Request
DELETE /apiv2/batch/events?type=private&apiKey=<api-key>&apiSign=<api-sign> \
--header 'Content-Type: application/json' \
--data '{
"ids" :[
"69437209a385c864541a4ae5",
"69437209a385c864541a4ae3"
]
}'
Example Response
{
"status": "207",
"statusLong": "Multi-Status",
"total_processed": 2,
"deleted": 0,
"not_found": 2,
"not_deleted": 0,
"errors": 0,
"items": [
{
"id": "69437209a385c864541a4ae5",
"status": 200
},
{
"id": "69437209a385c864541a4ae3", ⬅
"status": 404 ⬅
}
],
"elapsed": "6.84325ms"
}
The Batch API accepts payloads up to 1MB total size. Exceeding this triggers a
413 Payload Too Large error.
Check payload size before sending or split into smaller chunks and retry on 413 errors. Aim for
around 900KB.
You can use JSON.stringify(payload).length to check before sending or split
payload and retry smaller chunks in case you get 413 status.
1MB limit subject to future increases—monitor changelog for updates.
The Private Batch API enforces a 1 request per second rate limit. Exceeding this triggers a
429 Too Many Requests.