Skip to content

Triggers

Triggers are LyftData’s registry of curated “run this now” actions. A Trigger is a named invocation with schema-validated parameters that dispatches a message to a target job. This gives teams a safer alternative to handcrafting low-level message traffic when they want to run smoke checks, one-off operational workflows, or self-serve actions on demand.

When to use Triggers

Use Triggers when you want:

  • A discoverable list of “ready to run” actions for operators or teammates.
  • Parameter validation (a schema) instead of free-form JSON.
  • Policy and guardrails around who can invoke what.
  • An auditable invocation record with a stable invocation_id you can poll.

If you want something to run on a schedule, use the normal scheduling primitives (including the DSL trigger input) instead of a manual invocation surface.

How Triggers work

At a high level:

  1. An admin publishes a Trigger definition (slug, schema, defaults, and target job).
  2. A user or admin invokes the Trigger with parameters.
  3. LyftData dispatches a tagged user-generated message to the target job.
  4. The invocation is tracked and can be polled by invocation_id until it reaches a terminal state.

Trigger definitions can be published to one or more surfaces (UI and/or MCP). Invocation access is still enforced by server-side permissions and the trigger’s invocation policy.

Publish and manage Triggers (admin)

Triggers are intended to be curated. In most teams, admins publish and revise Triggers, and users/operators only invoke the approved set.

When publishing a Trigger, you typically define:

  • Slug + display name: the stable identifier and user-facing name.
  • Parameter schema + defaults: JSON schema for validation and optional default parameters.
  • Target job + tag: which job receives the invocation message (and optionally a specific message tag). Use a tag if the target job needs stable message-trigger filtering.
  • Publication surfaces: whether the Trigger should appear in the UI and/or as an MCP tool.
  • Invocation policy: who can invoke it, and any per-user rate limits.
  • Optional response slot + timeout: configure response_slot when the caller should receive a structured result (see “Trigger result shaping” below).
  • Optional proxy policy: if enabled, the dispatch message carries a trigger_proxy envelope so sinks (for example http-post) can capture and report a proxy-style response.

Triggers are revisioned. If you change a Trigger’s definition, you publish a new revision; you can also deactivate a Trigger so it no longer appears as invokable.

Publish and revise over the API

If you prefer to treat Triggers as a deployable artifact, the admin API (admin principals) exposes publish/revise/deactivate endpoints:

  • Publish (creates revision 1): POST /api/triggers/publish
  • Revise (creates a new revision): POST /api/triggers/<slug>/revise
  • Deactivate (marks inactive): POST /api/triggers/<slug>/deactivate

Example: publish a tenant-scoped Trigger with a response slot and proxy policy (note that proxy modes require response_slot):

{
"slug": "smoke_trigger",
"tenant_id": "tenant-a",
"display_name": "Smoke trigger",
"description": "Run a smoke check and return a structured result.",
"parameter_schema": {
"type": "object",
"properties": {
"env": { "type": "string" },
"depth": { "type": "integer", "minimum": 1 }
},
"required": ["env"]
},
"default_params": { "depth": 1 },
"target_job_name": "smoke_trigger_handler",
"target_message_tag": "smoke.invoke",
"publication": { "mcp": true, "ui_admin": true, "ui_user": false },
"invoke_policy": { "allow_admin": true, "allow_user": true },
"response_slot": "smoke.reply",
"response_timeout_seconds": 30,
"proxy_policy": { "mode": "passthrough", "timeout_seconds": 30 }
}

To revise a Trigger, send the full draft again to the revise endpoint (the server does not patch-in-place; it creates a new revision). For platform-scoped Triggers, use tenant_id: "platform" (platform-scoped principals only).

Trigger Dispatch Payload (trigger_invoke)

Trigger invocations are delivered to the target job as a user-generated message whose payload (job_event) has a stable top-level shape:

{
"type": "trigger_invoke",
"version": 1,
"invocation_id": "",
"trigger": {
"id": "",
"slug": "smoke_trigger",
"revision": 3,
"tenant_id": "tenant-a"
},
"caller": {
"username": "alice",
"role": "admin"
},
"params": { "env": "prod", "depth": 2 },
"requested_at_ns": 1739370000000000000
}

If the Trigger revision has proxy mode enabled, the payload also includes a trigger_proxy object (tenant/id/slot/nonce plus policy and bounds). Jobs can pass that object through to sinks; some sinks emit correlated trigger-proxy-response messages automatically.

In a message-triggered job run, this payload is available through ${msg|...} (see Variable Expansion).

Wire Up The Target Job

Most Trigger target jobs are message-triggered so they run only when invoked.

Example: a single-event job that runs on a tag and can read parameters via ${msg|job_event.params.*}.

name: smoke_trigger_handler
input:
echo:
trigger:
message:
filter-kind: user
filter-type: [user-generated]
filter-tag: smoke.invoke
json: true
event: "{}"

Trigger Result Shaping (response_slot)

By default, a Trigger invocation is dispatch-only: the server records whether the invocation message was accepted for dispatch, but it does not wait for a structured result from the workflow.

If you configure a response_slot on the Trigger revision, LyftData switches to response-slot mode:

  • The server keeps the invocation state in dispatched after message dispatch.
  • The invocation reaches a terminal state only when the server observes a correlated trigger_response message for the same invocation_id and response_slot, or when the response timeout is reached.
  • The invocation result is shaped to include the structured response payload.

Emit A trigger_response From A Job

To complete an invocation in response-slot mode, emit a trigger_response payload through the message bus.

Practical rules:

  1. The payload is strict: it must match the v1 envelope exactly (extra top-level keys are rejected).
  2. response_slot must match the Trigger revision’s configured slot.
  3. emitted_at_ns must be a number. If you build it from ${...} expansions, use convert with conversion: json (not num) to preserve integer precision for nanosecond timestamps.

This pattern keeps your job’s normal output separate from the Trigger response envelope by using output.message.input-field.

name: smoke_trigger_handler
input:
echo:
trigger:
message:
filter-kind: user
filter-type: [user-generated]
filter-tag: smoke.invoke
json: true
event: "{}"
actions:
- add:
output-fields:
trigger_response:
type: trigger_response
version: 1
tenant_id: "${msg|job_event.trigger.tenant_id}"
invocation_id: "${msg|job_event.invocation_id}"
response_slot: "smoke.reply"
nonce: "${msg|job_event.invocation_id}"
outcome: succeeded
result:
ok: true
message: "Smoke check passed"
emitted_at_ns: "${msg|job_event.requested_at_ns}"
- convert:
conversions:
- field: trigger_response.emitted_at_ns
conversion: json
output:
message:
input-field: trigger_response

If you need to report failure, set outcome: failed and include error_code and/or error_message (you can still include a result object for structured details).

What Callers Receive

The invoke call (UI/MCP/API) returns an invocation record. When a response is observed, the server stores it under invocation.result.trigger_response, and the invocation transitions to succeeded or failed.

If no response is observed before the configured response deadline, the invocation transitions to timed_out and the result is shaped with the deadline.

Proxy Capture (trigger_proxy_response)

If the Trigger revision has proxy mode enabled, the dispatch payload includes a trigger_proxy envelope. When that envelope is present:

  • Pass the envelope through as part of the event you send to the sink (outputs read event fields, not ${msg|...} bindings).
  • The http-post output captures allowlisted response headers and a bounded body and emits a trigger_proxy_response message automatically.
  • The server records it under invocation.result.trigger_proxy_response (plus a trigger_proxy_recorded_at timestamp) when the correlation matches.

Recording a proxy response does not complete the invocation by itself. If you want the invocation to reach a terminal state quickly, emit a trigger_response as well.

Example: copy the trigger_proxy object from the Trigger dispatch payload into the current event (and parse it back into JSON) so http-post can observe it:

- add:
output-fields:
trigger_proxy: "${msg|job_event.trigger_proxy}"
- convert:
conversions:
- field: trigger_proxy
conversion: json

Invoke Triggers in the UI

The Triggers UI is the “run now” surface:

  • Pick a Trigger from the list.
  • Fill in the schema-defined fields (defaults can be prefilled).
  • Run it and follow status by invocation_id until it completes.

Invoke Triggers over the API (automation)

For automation, the API exposes a list/invoke/status flow:

  • List invokable Triggers for a surface (for example UI): GET /api/triggers?surface=ui&invokable_only=true
  • Invoke a Trigger: POST /api/triggers/<slug>/invoke
  • Fetch status/result: GET /api/trigger-invocations/<invocation_id>

Invocations are asynchronous. If you need a definitive completion record, always poll by invocation_id, even if the initial invoke call returns quickly.

Invoke Request Body

POST /api/triggers/<slug>/invoke accepts a JSON body with optional control knobs:

{
"params": { "env": "prod", "depth": 2 },
"idempotency_key": "smoke-prod-2026-03-13T10:15:00Z",
"wait_timeout_seconds": 15,
"tenant_id": "tenant-a",
"callback": {
"url": "https://automation.example/hooks/lyftdata-trigger",
"headers": { "authorization": "Bearer …" }
}
}

Practical behavior:

  • params must be a JSON object. null is treated as {}.
  • tenant_id is required when the caller has multiple tenant memberships. Platform-scoped callers use an empty tenant scope.
  • idempotency_key deduplicates by (tenant scope, trigger slug, caller username, idempotency_key). A retry with the same key returns the original invocation_id instead of dispatching a second message.
  • wait_timeout_seconds controls how long the invoke endpoint waits before returning the current invocation record. Default: 15 seconds. Max: 300. It does not change how long the workflow has to produce a response.

Timeouts: Wait vs Response vs Proxy

These timeouts control different parts of the system:

  • wait_timeout_seconds (invoke request) bounds the API call’s “wait for a more up-to-date invocation record” behavior.
  • response_timeout_seconds (Trigger definition, only when response_slot is set) is the deadline for observing a correlated trigger_response before the invocation becomes timed_out (default: 15 seconds).
  • proxy_policy.timeout_seconds (Trigger definition, proxy mode) bounds the proxied sink attempt (for example, the http-post request timeout for capturing a trigger_proxy_response). Max: 300 seconds.

Callback Delivery (automation)

If you provide callback, the server posts invocation state updates to your URL as best-effort automation glue.

Callback request rules:

  • callback.headers must be a JSON object of string values (max 32 headers). Invalid header names/values are rejected.
  • Delivery is retried up to 5 times with backoff on transport errors and retryable status codes (408, 429, 5xx).
  • Callback delivery does not block invocation dispatch or completion.

Callback payload shape:

{
"version": 1,
"event_id": "",
"event_type": "trigger_invocation_state",
"emitted_at": 1739370000000000000,
"invocation": {
"invocation_id": "",
"trigger_slug": "smoke_trigger",
"trigger_revision": 3,
"tenant_id": "tenant-a",
"state": "dispatched",
"result": { "status": "accepted_for_dispatch", "mode": "response_slot", "state": "dispatched" }
}
}

Invoke Triggers from MCP (dynamic tools)

When a Trigger is published for MCP, it appears as a tool in your MCP client. These tools are generated dynamically from the registry at tool-list time, and tool names are derived from the Trigger slug, for example trigger_invoke__smoke_trigger.

Operationally:

  • Trigger tools are write-gated and require starting the MCP server with --allow-write.
  • Trigger tools show up only if the Trigger is published for MCP and your identity is allowed to invoke it.
  • The tool returns an invocation_id; use trigger_invocation_get to fetch status/result.
  • Trigger tools accept the Trigger’s schema fields plus reserved control keys. Reserved keys: _idempotency_key (dedupe retries) and _wait_timeout_seconds (default 15, max 300).

Where to go next

  • Workflows for message edges and control-plane composition.
  • Message Bus for how the aggregator message model maps into job DSL (internal-messages and output.message).
  • Messages for the live UI stream (channels, filters, export).
  • MCP Overview for setup, auth, and why trigger tools are treated as writes.