Private Batch Events

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.

In this Article:

Pre-requisites

A subscriber’s ecal_id is required for many actions, including retrieving Private Events. To ensure you capture this, follow the steps below:

  1. Configure Widget for Private Schedules
  2. Configure Webhook to recieve Subscriber’s ecal_id
  3. Authenticate

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.

Retrieving Private Batch Events

Retrieving Events by Subscriber ID

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

Retrieving Events by Reference Type

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

Retrieving Events by Subscriber ID and Reference Type

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

Retrieving Events by list of Events IDs

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

Adding Private Batch Events

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:

1. Duplicate Reference in Payload

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"
}

2. Duplicate Reference in Database

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"
}

3. Inactive/Unsubscribed Subscribers

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"
}

4. Invalid Subscribers

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"
}

5. Invalid Input Data

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.

Deleting Private Events

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.

1. Empty Body

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"
  }
}

2. Invalid Event IDs

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"
  }
}

3. Duplicate Event IDs in Payload

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"
}

4. Non-Existent Event IDs

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"
}

Notes:

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

  2. The Private Batch API enforces a 1 request per second rate limit. Exceeding this triggers a 429 Too Many Requests.