Workflows & triggers
Save a sequence of tool calls, and act on events from connected apps and public data.
Define a workflow
A workflow has a slug (2–40 lowercase letters, digits or underscores, starting with a letter; required when you create it), a title, a description, an inputSchema and 1–20 steps. Each step is { id, tool, arguments, provider? }. In arguments, {"$input": "topic"} takes a run input and {"$step": "search", "path": "/results/0/url"} takes a value from an earlier step’s output.
{
"slug": "research_brief",
"title": "Research brief",
"description": "Search the web for a topic and read the top result.",
"inputSchema": {
"type": "object",
"properties": { "topic": { "type": "string" } },
"required": ["topic"]
},
"steps": [
{ "id": "search", "tool": "web_search",
"arguments": { "query": { "$input": "topic" } } },
{ "id": "read", "tool": "scrape_page",
"arguments": { "url": { "$step": "search", "path": "/results/0/url" } } }
]
}Save it with POST /v1/workflows. Every save is a new version and the newest 50 are kept.
Run a workflow
Start a run with inputs, a maximumChargeMicros budget and an Idempotency-Key. Each step is quoted and bought in order within what is left of the budget.
curl "$RELVO_API_URL/v1/workflows" \
-H "Authorization: Bearer $RELVO_API_KEY" \
-H "Content-Type: application/json" \
--data @workflow.json
curl "$RELVO_API_URL/v1/workflows/research_brief/runs" \
-H "Authorization: Bearer $RELVO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: research-brief-001" \
--data '{"inputs":{"topic":"content marketing"},"maximumChargeMicros":200000}'A step that no longer fits the budget stops the run with platform_workflow_budget_exceeded; earlier steps stay charged. Read the run with GET /v1/workflow-runs/{id} and cancel it with POST /v1/workflow-runs/{id}/cancel. A project holds up to 50 workflows and runs up to 10 at once.
Triggers
A poll trigger runs a read operation of a connected app every 1m, 5m (default), 15m or 1h and emits added for each new item, or changed when a single result changes. A webhook trigger uses a type such as github.push: Relvo registers the webhook through your connection. The first poll sets the baseline and emits nothing.
{
"kind": "poll",
"tool": "github__issues_list_for_repo",
"arguments": { "owner": "octo", "repo": "app" },
"connectionId": "CONNECTION_ID",
"interval": "5m",
"destinations": [{ "kind": "webhook", "url": "https://YOUR_SERVER/relvo/events" }]
}curl "$RELVO_API_URL/v1/triggers" \
-H "Authorization: Bearer $RELVO_API_KEY" \
-H "Content-Type: application/json" \
--data @trigger.jsonGET /v1/trigger-types?app=github lists an app’s webhook types and the operations you can poll. A trigger runs as the API key that created it, and revoking that key stops the trigger.
Watch public data
A monitor re-runs a single-step tool that reads shared public data on a schedule (15m, 1h, 1d or a 5-field cron, at least 15 minutes apart) and emits only what changed. In the app it appears under Triggers.
Price it with POST /v1/monitors/quote, then create it with POST /v1/monitors and a maximumChargeMicrosPerRun of at least the quoted chargeCeilingMicros, plus 300 micros if it should keep running after your included calls are used. A monitor runs and charges until you delete it. It pauses while the balance, a spending limit or that maximum cannot cover a run, and resumes by itself once a run fits again. It also pauses when the price rises above your maximum, the project’s tool execution no longer allows the tool, or its creator loses access to the project; those pauses need POST /v1/monitors/{id}/resume. Revoking the key that created it does not stop it.
Destinations
Add up to 5 destinations when you create a trigger, or later with POST /v1/triggers/{id}/destinations:
webhook
Each event as a signed JSON POST to your HTTPS URL.
workflow
Starts one run of a workflow per event, with the event as its event input and maximumChargeMicros as each run's budget.
slack / discord
A summary of up to 10 new events with a link to the app, sent to an incoming webhook URL.
email
A summary to team members. Added only in the app.Failed deliveries (408, 429, 5xx and network errors) are tried up to 6 times in all. Retries wait 30 seconds and double each time, up to 8 minutes; a 429’s Retry-After is honored up to an hour. Other 4xx responses fail at once. A delivery can arrive twice, so deduplicate on Relvo-Delivery.
Verify webhook signatures
Each webhook delivery has a Relvo-Signature: t=<unix seconds>,v1=<hex> header and a Relvo-Delivery id. v1 is the HMAC-SHA256 of <t>.<raw body> with the destination’s signing secret, which is returned only once, when you add the destination. Check the timestamp and compare signatures in constant time.
import { createHmac, timingSafeEqual } from "node:crypto";
// rawBody is the request body exactly as received, before JSON parsing.
export function verifyRelvoSignature(header, rawBody, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(",").map(part => part.split("=", 2)));
const timestamp = Number(parts.t);
const age = Math.abs(Date.now() / 1000 - timestamp);
if (!Number.isInteger(timestamp) || age > toleranceSeconds) return false;
const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest();
const received = Buffer.from(parts.v1 ?? "", "hex");
return received.length === expected.length && timingSafeEqual(received, expected);
}Events, pricing and limits
GET /v1/triggers/{id}/events and GET /v1/trigger-events return events oldest first after after; pass nextAfter as the next after. Without after you get the newest events; pass after=0 to start from the oldest kept event. Events are kept for 30 days, up to 10,000 per trigger.
Each poll is one tool call: it counts toward your plan’s included calls and then costs 300 micros. Receiving webhooks is free, email costs 900 micros per recipient, and a workflow destination is charged per step. Up to 50 triggers per project and 100 per team, and 25 monitors per project.
Endpoints
GET /v1/workflows · tools:read
List workflows
POST /v1/workflows · tools:execute
Save a new workflow
GET /v1/workflows/{slug} · tools:read
Read one workflow
POST /v1/workflows/{slug}/update · tools:execute
Replace it as a new version (send expectedVersion)
POST /v1/workflows/{slug}/delete · tools:execute
Delete it
POST /v1/workflows/{slug}/runs · tools:execute
Start a run
GET /v1/workflow-runs/{id} · executions:read
Read a run
POST /v1/workflow-runs/{id}/cancel · tools:execute
Cancel a run
GET /v1/trigger-types?app={app} · tools:read
Webhook types and pollable operations of an app
GET /v1/triggers · executions:read
List triggers
POST /v1/triggers · tools:execute
Create a trigger
POST /v1/triggers/{id}/update · tools:execute
Enable, disable or rename it
POST /v1/triggers/{id}/delete · tools:execute
Delete it
POST /v1/triggers/{id}/destinations · tools:execute
Add a destination
GET /v1/triggers/{id}/events · executions:read
Events of one trigger
GET /v1/trigger-events · executions:read
Events of every trigger
POST /v1/monitors/quote · tools:read
Price a monitor without creating it
GET /v1/monitors · executions:read
List monitors
POST /v1/monitors · tools:execute
Create a monitor
POST /v1/monitors/{id}/resume · tools:execute
Resume a paused monitor
POST /v1/monitors/{id}/delete · tools:execute
Delete it
GET /v1/monitor-events · executions:read
Events of every monitorThese endpoints are under /v1 on the HTTP API and use the same keys and scopes.