Triggers

Stewardship triggers automate task creation based on contact filters and execution schedules. When a trigger fires, it identifies matching contacts (applying cooldown and monthly limits), generates AI message variants using a linked prompt template, and creates stewardship tasks for the assigned user.

All endpoints require authentication via the X-API-Key header. See Authentication.


How triggers work

  1. Filter: The trigger's conditions define which contacts match (same AND/OR filter logic as boards).
  2. Schedule: The trigger fires on its executionSchedule (hourly, daily, weekly, or monthly) at the specified executionTime and timezone.
  3. Limits: Contacts are excluded if they had a task created within cooldownDays or already have maxTasksPerContactMonth tasks this month. At most maxTasksPerRun tasks are created per execution.
  4. AI generation: For each matching contact, the system generates message variants using the linked prompt template (base template merged with any custom overrides).
  5. Task creation: A stewardship task is created for each contact, assigned to assignedUserId.

Triggers are scheduled via GCP Cloud Tasks. Status changes and scheduling field updates automatically cancel and reschedule the Cloud Task.


Create a trigger

POST /triggers

Maximum 100 triggers per organization. If status is active, a GCP Cloud Task is scheduled immediately.

Request body

FieldTypeRequiredNotes
namestringYes
descriptionstringYes
categorystringYesretention, stewardship, engagement, or custom.
colorstringYesHex color code.
statusstringYesactive or draft.
prioritystringYesPriority level.
assignedUserIdstringYesUser ID to assign generated tasks to.
executionSchedulestringYeshourly, daily, weekly, or monthly.
executionTimestringYesTime of day in HH:MM format.
executionTimezonestringNoIANA timezone (e.g. America/New_York).
executionDaynumberConditionalRequired for weekly (1–7, Monday=1) or monthly (1–31). Null otherwise.
maxTasksPerRunnumberYesMin 1. Maximum contacts to process per execution.
cooldownDaysnumberYesMin 0. Days to wait before re-targeting a contact.
maxTasksPerContactMonthnumberYesMin 1. Monthly cap per contact.
conditionsobjectYesContact filter object (AND/OR criteria).
promptSettingsobjectYes{ promptTemplateId, customOverrides }.
createdBystringYesUser ID.

Example request

curl -X POST https://<host>/api/triggers \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "High-value donor follow-up",
    "description": "Weekly outreach to high-value donors",
    "category": "stewardship",
    "color": "#4A90D9",
    "status": "active",
    "priority": "high",
    "assignedUserId": "675a...",
    "executionSchedule": "weekly",
    "executionTime": "09:00",
    "executionTimezone": "America/New_York",
    "executionDay": 1,
    "maxTasksPerRun": 10,
    "cooldownDays": 14,
    "maxTasksPerContactMonth": 2,
    "conditions": {
      "type": "AND",
      "criteria": [
        { "field": "totalTransactionsAmount", "operator": "greater_than", "value": 5000 }
      ]
    },
    "promptSettings": {
      "promptTemplateId": "675a...",
      "customOverrides": {}
    },
    "createdBy": "675a..."
  }'

Response

{
  "success": true,
  "data": {
    "_id": "675a...",
    "name": "High-value donor follow-up",
    "status": "active",
    "executionSchedule": "weekly",
    "executionTime": "09:00",
    "executionDay": 1,
    "nextRunAt": "2026-03-30T13:00:00.000Z",
    "taskName": "projects/.../tasks/...",
    "tasksCreatedCount": 0,
    "lastRunAt": null,
    "createdAt": "2026-03-24T12:00:00.000Z"
  }
}

Errors

StatusMessageCause
400Validation errorMissing required fields or invalid values.
400Max triggers reachedOrganization already has 100 triggers.

List triggers

GET /triggers

Returns triggers sorted by createdAt descending. Each trigger includes the merged prompt template (base template fields merged with promptSettings.customOverrides).

Query parameters

ParameterTypeDefaultNotes
statusstring—Filter: active, paused, draft, error.
categorystring—Filter by category.
prioritystring—Filter by priority.
executionSchedulestring—Filter: hourly, daily, weekly, monthly.

Example request

curl "https://<host>/api/triggers?status=active" \
  -H "X-API-Key: your-api-key"

Response

{
  "success": true,
  "data": [
    {
      "_id": "675a...",
      "name": "High-value donor follow-up",
      "status": "active",
      "category": "stewardship",
      "executionSchedule": "weekly",
      "executionTime": "09:00",
      "nextRunAt": "2026-03-30T13:00:00.000Z",
      "tasksCreatedCount": 24,
      "lastRunAt": "2026-03-23T13:00:00.000Z",
      "promptSettings": {
        "promptTemplateId": "675a...",
        "customOverrides": {},
        "promptTemplate": {
          "name": "Stewardship outreach",
          "type": "sms",
          "tone": "warm_personal"
        }
      }
    }
  ]
}

Get a trigger

GET /triggers/:id

Returns the trigger with its merged prompt template.

Path parameters

ParameterTypeDescription
idstringTrigger ID.

Example request

curl "https://<host>/api/triggers/675a..." \
  -H "X-API-Key: your-api-key"

Errors

StatusMessageCause
404Not foundTrigger does not exist.

Update a trigger

PUT /triggers/:id

All fields are optional. If status changes to/from active, or scheduling fields change while active, the GCP Cloud Task is cancelled and rescheduled accordingly.

Path parameters

ParameterTypeDescription
idstringTrigger ID.

Request body

All fields from create are accepted as optional. status additionally allows paused and error.

Example request

curl -X PUT https://<host>/api/triggers/675a... \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "paused"
  }'

Response

Returns the updated trigger with merged prompt template.

Errors

StatusMessageCause
404Not foundTrigger does not exist.

Delete a trigger

DELETE /triggers/:id

Cancels any scheduled GCP Cloud Task.

Path parameters

ParameterTypeDescription
idstringTrigger ID.

Example request

curl -X DELETE https://<host>/api/triggers/675a... \
  -H "X-API-Key: your-api-key"

Response

{
  "success": true,
  "message": "Success"
}

Errors

StatusMessageCause
404Not foundTrigger does not exist.

Response envelope

All endpoints use the standard envelope:

{
  "error": false,
  "success": true,
  "message": "...",
  "code": 200,
  "data": "...",
  "pagination": {}
}

Error responses:

{
  "error": true,
  "success": false,
  "message": "...",
  "code": 400,
  "data": null
}

data is null for a general failure, such as a database error or an upstream timeout. When the request is rejected because of a specific field, data carries the field(s) at fault instead:

{
  "error": true,
  "success": false,
  "message": "phone: Phone parse failed: The string supplied did not seem to be a phone number (input: <masked>)",
  "code": 400,
  "data": {
    "issues": [
      {
        "path": "phone",
        "message": "Phone parse failed: The string supplied did not seem to be a phone number (input: <masked>)"
      }
    ]
  }
}

Each issue carries a path (the dotted path into the request body) and a message. A label is added when the path is not self-describing, which today means custom fields only, for example "path": "fields.68b0a1f39c2e4d0012ab77e5", "label": "Class Year". issues is an array, but treat it as holding at most one entry: validation stops at the first failure.

On this page