Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.gainable.dev/llms.txt

Use this file to discover all available pages before exploring further.

The closed vocabulary

Every playbook starts from one of exactly four trigger types. Nothing else. The closed vocabulary is deliberate. It keeps agents auditable, predictable, and easy to reason about.
TriggerStarted byUse for
ScheduleCron expressionDaily briefings, weekly digests, time-based SLA checks
Data changeChange-stream events on a collectionReactive work tied to record state
WebhookInbound HTTP from an external systemStripe events, SendGrid bounces, GitHub issues
User-triggeredExplicit button click in the app”Draft me a recap,” “Run this playbook now”

Schedule

A cron-style schedule. The runtime fires the playbook at the specified time.
trigger:
  type: schedule
  cron: "0 9 * * 1-5"   # 9 AM, Monday to Friday
  timezone: "America/New_York"

Common cron patterns

PatternMeaning
0 8 * * *Every day at 8 AM
0 9 * * 1-5Weekdays at 9 AM
0 */4 * * *Every 4 hours
0 0 1 * *First of every month at midnight
*/15 * * * *Every 15 minutes

Timezone handling

For personal-scope playbooks, the timezone resolves to the user’s timezone. For app-wide playbooks, the timezone is set on the trigger.
trigger:
  type: schedule
  cron: "0 8 * * 1-5"
  timezone: "{{ user.timezone }}"   # personal scope

When to use schedule

  • Recurring digests, briefings, and reports
  • Time-based SLA enforcement (“any open ticket older than 24 hours”)
  • Periodic refreshes (“recompute pipeline forecast every Monday”)

Data change

A change-stream event on a collection. The runtime observes inserts, updates, and deletes, and fires the playbook when an event matches the filter.
trigger:
  type: data_change
  collection: deals
  on: [update]
  where: "stage = 'lost' AND amount > 10000"

Operations

OperationFires when
createA new record is inserted
updateAn existing record’s fields change
deleteA record is removed
You can listen to multiple operations in one trigger:
trigger:
  type: data_change
  collection: tickets
  on: [create, update]
  where: "priority = 'p0'"

Field-level filters

The where clause can reference the new state of the record. For updates, it can also reference what changed:
trigger:
  type: data_change
  collection: deals
  on: [update]
  where: "changed('stage') AND new.stage = 'lost'"

When to use data change

  • Reactive work tied to a state change (“when a deal moves to lost…”)
  • Onboarding flows (“when a new contact is created…”)
  • Anomaly detection (“when an order’s total exceeds $50,000…”)
The change-stream pattern is the same one Gainable uses internally for the email daemon and other observation services. The runtime does the heavy lifting; the playbook just describes the filter.

Webhook

An inbound HTTP request from an external system. The runtime exposes a unique URL per webhook and fires the playbook when a request arrives.
trigger:
  type: webhook
  source: stripe
  event: invoice.payment_failed
  secret: "{{ secrets.stripe_webhook }}"

How webhook URLs work

When you configure a webhook trigger, Gainable provisions a URL like:
https://api.gainable.dev/webhooks/{app_id}/{trigger_id}
You configure the source system to POST to that URL. The runtime verifies the signature (when applicable), parses the body, and exposes it as trigger.body to the playbook.

Common sources

SourceExample events
Stripeinvoice.payment_failed, customer.subscription.deleted
SendGridbounce, spamreport
GitHubpull_request.opened, issue_comment.created
Calendarevent.created, event.updated
GenericAny system that can POST JSON

When to use webhook

  • Reacting to events in external systems you don’t own
  • Bridging Gainable apps to third-party tools
  • Receiving callbacks from long-running external jobs
See Connect outbound for the full inbound and outbound integration story.

User-triggered

An explicit button click in the app. A user opens a record (or a page) and clicks a button labeled “Run this playbook now.”
trigger:
  type: user_triggered
  button_label: "Draft recap"
  surfaces: [deal_detail_page, dashboard]

How it appears

Gainable renders user-triggered playbooks as buttons in the Autopilot widget on the page you specified. When the user clicks:
  1. The runtime fires the playbook with trigger.user_id set to the clicker
  2. The playbook runs with that user’s permissions
  3. The resulting draft (if any) lands in their personal Autopilot inbox

When to use user-triggered

  • On-demand drafts (“write me a follow-up for this deal right now”)
  • Bulk operations the user wants to start manually
  • Anything the user wants control over the timing of, even if the rest of the playbook is automatic

Choosing the right trigger

QuestionAnswer
Does this need to happen at a specific time of day?Schedule
Does this react to a record changing?Data change
Does this come from an external system?Webhook
Should the user choose when this runs?User-triggered
If two answers are yes, you probably have two playbooks pretending to be one. Split them.

Best practices

A schedule trigger that scans a collection every 5 minutes is almost always worse than a data_change trigger that fires only on the relevant transition. Faster, cheaper, and more accurate.
The more selective the trigger’s where clause, the less work the runtime does and the cleaner your action log gets.
A daily briefing fired at 8 AM UTC is 3 AM in California. Bind the timezone to the user.
Webhook triggers can be tested by replaying captured payloads. Always do this before going live.

Learn more

Playbooks

Trigger, steps, and guardrails together

Tools

What playbooks call after the trigger fires

Connect outbound

Webhooks and external systems

Inbox

Where user-triggered buttons render