For AI agents: a documentation index is available at https://docs.plungeai.com/llms.txt. Append .md to any page URL, or send Accept: text/markdown, to get markdown. Setup instructions for agents are at https://docs.plungeai.com/agents.md. One ozk_ key opens every plane, models included.

Documentation Index: fetch the complete documentation index at /llms.txt. Use this file to discover all available pages before exploring further.

A trigger starts a bot from an event outside the platform. Every trigger hangs off a schedule of the bot (see Schedules and wake), so the run goes through the same stored policy, budget and delivery as any other run. Hooks go the other way: they send the bot's run events to a URL you own.

Inbound

TriggerYou getWhere to set it
WebhookAn https URL and a secretREST, MCP, CLI
EmailAn addressREST, MCP, CLI
Slack channel or keywordA bot started by a messageStudio Triggers, Slack card
Gmail new mailA bot started once per new mailStudio Triggers, Gmail card
GitHub or GitLab eventA webhook preset with an event allow-listStudio Triggers, Git card (the Studio route to a webhook)

The REST routes live under /v1/triggers/{kind}. Every call names the schedule with schedule_id: in the body for POST, in the query for GET and DELETE. Mint answers 201 with Cache-Control: no-store. A secret is in the mint response once and is never listed again. Revoking an id that is not on the schedule is 404 trigger_not_found. Webhook and email triggers also rotate: POST /v1/triggers/{webhook|email}/{token}/rotate mints a successor with the same settings and a new secret, then revokes the old token.

Webhook

curl -s -X POST https://api.plungeai.com/v1/triggers/webhook \
  -H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
  -d '{"schedule_id":"<id>","scheme":"github","prompt_template":"New PR: {pull_request.title}","filters":{"all":[{"path":"action","op":"equals","value":"opened"}]}}'

The response carries token, secret, scheme and the url to give the sender.

FieldMeaning
schemeHow the sender signs: generic (default, X-Webhook-Signature is the hex HMAC-SHA256 of the raw body), github, gitlab or standard-webhooks.
prompt_templateThe run's input. {dotted.path} fills in a field of the JSON payload.
filters{all?, any?} of rules {path, op, value?}, up to 20. op is equals, contains, exists or in. A path that starts with headers. reads a request header.

What the sender gets back, in order:

AnswerWhen
401Bad signature, or a timestamp more than 300 seconds off.
409A delivery id seen in the last hour.
204The filters did not match. The delivery is acknowledged, no run starts and nothing is spent.

Email

POST /v1/triggers/email with {"schedule_id": "..."} returns a token and an address_local_part of the form bot+<token>; the address is that local part at the Email Routing domain your operator set up. Mail to it starts the schedule's bot. allowed_from (comma-separated addresses or domains) restricts the senders; it is a sender filter, weaker than a webhook signature.

Slack channel or keyword

Answers 503 slack triggers not enabled yet until the platform owner enables it; the API documents the code.

In Studio, open the bot's Triggers and add a Slack trigger: a channel, and optionally a keyword. A human message in the channel that contains the keyword (the longest keyword wins; no keyword means every message) starts the bot with the input from <@author> in <#channel>: text. The run is signed as the trigger owner, so the bot's policy and the owner's budget apply.

  • App mentions, bot posts, edits and other subtypes never match, so there is no loop. Thread replies in the channel do fire.
  • Creating the trigger requires your own membership of the channel, checked server-side and fail-closed.
  • The channel can be added to the job's delivery, so the result lands there.
  • Anyone who can post in the channel spends the owner's budget: pick channels you trust.

Gmail new mail

In Studio, add a Gmail trigger: a Gmail search such as from:billing@ subject:invoice and a cadence of 5, 15 or 60 minutes. Studio saves a small check workflow and a heartbeat schedule. Each tick the scheduler runs the check as the owner, using the owner's own Google connection, and a YES starts the bot once per mail it has not started before, with the mail (From, Subject, Date, id and the plain-text body, up to 8,000 characters) as the run's input.

  • A mail starts at most one run. Only mail that arrived after the trigger was created counts, with at most 5 runs per tick.
  • A failed run is not retried, because a retry could repeat side effects. It shows in the bot's run history.
  • This is a poll: the cadence is the latency, and the YES or NO check costs one small model call per tick. A tick that finds no new mail puts nothing on the board.
  • Mail content is untrusted input. Choose searches you trust and keep the bot's tools narrow.

GitHub and GitLab

The Git card mints a github or gitlab webhook with an allow-list of events, then shows the URL and the secret once and where to paste them: in GitHub, repository Settings, Webhooks, Payload URL and Secret with application/json; in GitLab, Settings, Webhooks, URL and Secret token. Events outside the allow-list, including GitHub's ping, are acknowledged and ignored.

Deliver-only

A job with parameters.deliver_only: true never calls the engine or a model. When its trigger fires, the scheduler fills parameters.template ({input} is the incoming text) and posts it to the job's deliver[] targets, so a webhook can be forwarded to a channel at no model cost. The run history shows the filled text and no cost.

  • Slack and Discord pings and links in the incoming text (@everyone, <!channel>, <@U...>, <https://x|label>) are broken with an invisible character. Your own template is posted as written.
  • Other markup passes through, so put a filters allow-list on the trigger, and allowed_from on an email trigger.
  • A deliver-only job needs a non-empty deliver[] and a template.

Outbound hooks

Answers 503 unavailable until the platform owner enables it; the API documents the code.

A hook sends the schedule's run events to an https URL you own.

curl -s -X POST https://api.plungeai.com/v1/triggers/hooks \
  -H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
  -d '{"schedule_id":"<id>","url":"https://example.com/ocean-hook","events":["run.finished","run.failed"]}'

The response carries the hook id and a secret, shown once. The URL must be https, public, with no credentials in it, and it is re-checked before every delivery. A schedule has at most 10 hooks.

EventWhen
run.startedThe run began.
run.finishedThe run completed.
run.failedThe run failed, including an agent refusal that the history records as failed.
run.needs_approvalThe run stopped on a held action.

The body is {event, delivery_id, timestamp, job_id, job_name, workflow_id, execution_id, run_id, status, result_summary?, error?}. execution_id joins one run's events.

Each delivery carries three headers: X-Ocean-Delivery-Id, X-Ocean-Timestamp (unix seconds) and X-Ocean-Signature, which is sha256= plus the hex HMAC-SHA256 of <delivery id>.<timestamp>.<raw body> under the secret. Recompute it over the raw body, compare in constant time, and reject an old timestamp:

import { createHmac, timingSafeEqual } from 'node:crypto'

export function verify(secret, headers, rawBody) {
  const id = headers['x-ocean-delivery-id']
  const ts = headers['x-ocean-timestamp']
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false
  const want = 'sha256=' + createHmac('sha256', secret).update(`${id}.${ts}.${rawBody}`).digest('hex')
  const got = String(headers['x-ocean-signature'] ?? '')
  return got.length === want.length && timingSafeEqual(Buffer.from(got), Buffer.from(want))
}
  • Best effort. One retry after a second, only on a network error, a timeout (10 seconds), a 5xx, a 408 or a 429. Any other answer is final. The retry sends the identical body, id and signature, so dedupe on X-Ocean-Delivery-Id. Redirects are never followed. A hook failure never fails or delays the run.
  • Scope. Scheduler-path runs only: cron, heartbeat, run now and triggers. An ad-hoc run from Studio or the API emits nothing.
  • No ordering. Events are sent as they happen, each independently. Order by timestamp and join on execution_id.
  • One-time jobs. An @once job deletes itself when its run ends, and its hooks with it. The run's last event is still delivered.

Planned: TI-33

Search is not available yet. Until it ships, use the page index or browse the sidebar.

Planned: TI-34

The docs assistant is not available yet. You can hand these docs to your own assistant instead.