Triggers and automation
Оваа содржина сè уште не е достапна на вашиот јазик.
A trigger starts an agent or a workflow without anyone opening a chat. It runs on a schedule, or when another system calls a web address. This page covers triggers, and the workflow steps that make unattended runs useful: looping over a list, calling a web service, sending email, adding documents to a collection, reading and updating them, running a little Python, and using an integration tool.
For what a workflow is, see Workflows. For the steps every workflow can use, see Workflow steps.
Triggers
Section titled “Triggers”A trigger belongs to one agent or one workflow, and you manage it from that agent’s or workflow’s Triggers panel. You need edit access to the target. A workspace can have up to 50 triggers, and a trigger’s name can be up to 80 characters and is unique for its target.
Every trigger has:
- an input, up to 8 KB of text. It becomes the first message of each
run. In a workflow, you can put values from the run into it with
{{ ... }}, the same way as in steps. - optionally, notify, a list of people to email when a run finishes.
Two kinds exist: schedules and webhooks.
Schedules
Section titled “Schedules”A schedule runs the target at set times. You choose a frequency:
| frequency | what you set |
|---|---|
| every 15 minutes, every 30 minutes | nothing; runs start at the quarter or half hour |
| hourly | the minute past the hour, 0 to 59 |
| daily | a time of day, HH:MM, and optionally the days of the week |
| weekly | a time of day and at least one day |
| monthly | a time of day and a day of the month, 1 to 28 |
The day of the month stops at 28 so that every month has it. The shortest interval is 15 minutes.
Each trigger carries its own time zone, an IANA name such as
Europe/Skopje. A run at 08:00 stays at 08:00 on the clock across daylight
saving changes. The console fills in your browser’s time zone for a new
schedule.
When you build a schedule, the console shows the next five run times, so you can check it before saving. A run may start up to about 75 seconds after its time: up to a minute of spread, fixed per trigger, plus the scheduler’s check interval.
Two rules keep a schedule from piling up work:
- If a run of the same trigger is still going when the next slot comes, the slot is skipped and recorded as skipped.
- After downtime, a trigger runs once, not once for every slot it missed.
Webhooks
Section titled “Webhooks”A webhook trigger runs when something sends a POST to its URL. The URL
contains a secret token, so treat it like a password.
- The URL is shown once, when you create the trigger or rotate it. Copy it then. Afterwards the console shows only the first few characters of the token.
- Rotate gives the trigger a new URL and stops the old one working at once. Rotating makes you the trigger’s owner.
- An unknown, rotated or disabled URL answers
404 Not found. A body that is too large or of the wrong type is refused before the URL is looked up, with413,415or400, even if the URL is unknown.
A delivery is a JSON body (application/json) or a form
(application/x-www-form-urlencoded, read as a mapping of text values).
Other content types are refused with 415. The body can be up to 1 MB.
The run reads it as input.body, so your input text can include, for
example, {{ input.body.email }}.
The platform replies 202 with a delivery_id as soon as it has accepted
the delivery. The run itself starts shortly afterwards, and runs of one
webhook start one at a time. A trigger accepts 60 deliveries a minute and
holds at most 100 waiting; beyond either, it answers 429.
Signatures
Section titled “Signatures”You can require that a delivery is signed with a shared secret, so only the sender you trust can start runs. You choose a scheme and enter the secret in the trigger’s settings:
| scheme | what is checked |
|---|---|
github |
The X-Hub-Signature-256 header, sha256= followed by the hex HMAC-SHA256 of the raw body. |
stripe |
The Stripe-Signature header, t=<timestamp>,v1=<signature>, where the signature is the HMAC-SHA256 of the timestamp, a dot and the raw body. The timestamp must be within 5 minutes of now. |
generic |
A header you name, holding the hex HMAC-SHA256 of the raw body. A leading sha256= is accepted. |
A delivery with a missing or wrong signature is answered with 401 and
starts nothing. A signature is optional, but without one anyone who learns
the URL can start a run.
Owners
Section titled “Owners”A triggered run has no person at the keyboard, so it runs with the access of the trigger’s owner: their role, their departments and their access to the target. It is as if the owner ran it by hand, including what it counts against.
- Creating, editing, resuming or rotating a trigger makes you its owner. Creating and rotating need edit access to the target. Editing and resuming also check that you can run the target yourself.
- The owner’s access is checked again before every run.
A trigger pauses itself when:
- the owner is no longer a member of the workspace, or can no longer run the target; or
- three runs in a row fail.
A paused trigger shows its reason. The owner (while they still have an account) and the workspace’s admins get an email naming the trigger, its target and the reason. To start it again, someone who can run the target presses Resume, which makes them the owner and clears the failure count.
A run that is skipped because the previous one is still going is not a failure. A run that finishes successfully resets the count.
Notify
Section titled “Notify”A trigger can email people when a run finishes. Add up to 20 addresses and choose when:
- On failure (the default) mails only when a run fails. The email names the error class and links to the agent’s or workflow’s page.
- Always also mails when a run succeeds. The email holds the final reply, up to 20,000 characters, and a link to the agent’s or workflow’s page.
Each address must belong to a member of the workspace, or to a domain your workspace has verified. The rule is checked when you save and again when the notify email is queued; an address that no longer qualifies is skipped.
Secrets
Section titled “Secrets”Some steps need a key or token, such as an API key for a web service. Keep
them in the workspace’s Secrets settings rather than in a workflow.
Only workspace admins can add, replace or delete secrets. A value is never
shown again after you save it; the list shows a name, a mask and the allowed
hosts. The mask is •••• followed by the last 2 characters for a value of
12 or more characters, and just •••• for a shorter one.
- A name is capital letters, digits and underscores, starting with a letter, up to 64 characters. A workspace can hold 100 secrets, each up to 8 KB.
- Every secret has allowed hosts: 1 to 20 host names it may be sent
to, such as
api.stripe.com.*.example.comallows every subdomain but notexample.comitself, so list both if you need both. No scheme, port or path. - A workflow reads a secret as
{{ secrets.NAME }}, and only in an http step’s headers and auth. Anywhere else, including the URL, the workflow is refused at save. - A step that uses a secret must have a fixed host in its URL, and that host must be one of the secret’s allowed hosts. This stops someone who can edit a workflow from sending your key to a server they control. The check runs when you save the workflow and again before each request.
- A step’s record in the run history shows the header names and the kind of authentication, never a value.
Deleting a secret a workflow uses is allowed. That step then fails the next time it runs.
Steps for automation
Section titled “Steps for automation”These eight step kinds are for workflows that run unattended. They follow
the rules in Workflow steps: title and
on_error work on all of them, and so does retry, except on for_each
and loop, where it is refused; retry the steps inside them instead.
Looping over a list: for_each
Section titled “Looping over a list: for_each”A for_each step runs a small flow of its own once for each item in a
list. For example, summarise each new article in a feed.
summarise: kind: for_each items: "{{ fetch.items }}" where: all: - {left: "{{ item.published_ts }}", op: gt, right: "{{ run.last_success_ts }}"} concurrency: 5 body: steps: sum: kind: llm prompt: "Two sentences on {{ item.title }}: {{ item.summary }}" edges: - {from: start, to: sum}| field | |
|---|---|
items |
Required. A single reference to a list, with at most 1,000 entries. A longer list is a step error. |
where |
Optional. Conditions tested for each item before it runs. Items that fail are left out. |
concurrency |
How many items run at once, 1 to 10. Default 5. |
progress_label |
Optional. A template naming the item in the run’s progress, such as {{ item.filename }}. See Watching a run. |
body |
Required. The steps to run for each item, with their own edges starting at start. |
Inside the body, item is the current item and index its position,
counting from 0. Outside the step, only its output is visible:
results, a list with each item’s last result in order, and count.
Repeating until done: loop
Section titled “Repeating until done: loop”A loop step runs its body again until a condition holds, for example to
read a paged API.
pages: kind: loop max_iterations: 10 until: all: [{left: "{{ previous.body.next }}", op: empty}] body: steps: page: kind: http url: "https://api.example.com/items?cursor={{ previous.body.next }}" edges: - {from: start, to: page}max_iterations is required, 1 to 50. An optional progress_label names
each iteration in the run’s progress and can read iteration and
previous. until is tested after each
iteration. Without it the loop runs max_iterations times, and reaching the
maximum with until never true is not an error. Inside the body,
iteration is the current count from 0 and previous is the last
iteration’s output, empty on the first. The step’s output is result (the
last iteration’s) and iterations.
Rules for bodies
Section titled “Rules for bodies”- A body is its own flow, with the same naming and edge rules as the workflow.
- A container may sit inside another container’s body, but no deeper: one level of nesting.
- A body cannot write variables (
set) or send a reply to the chat. Results leave a body only through the container’s output. - Steps outside cannot read steps inside a body.
- A failing step in a body fails the container, naming the item and the step, and the remaining iterations stop.
- The run history records the steps of the first 20 iterations, plus every failed one. It also records how many items there were, how many ran and how many were recorded.
- Approval steps inside a body are not supported.
The http step
Section titled “The http step”An http step calls a web address and keeps the response.
fetch: kind: http method: GET url: "https://example.com/feed.xml" query: {since: "{{ run.last_success_at }}"} headers: {X-Api-Key: "{{ secrets.NEWS_KEY }}"} parse: auto| field | |
|---|---|
method |
GET (default), POST, PUT, PATCH or DELETE. |
url |
Required. A template; http or https only. |
query |
Values added to the URL, URL-encoded. |
headers |
Up to 30 headers. |
auth |
{bearer: ...}, or {basic: {username: ..., password: ...}}. |
body |
One of {json: ...}, {text: ...} or {form: {...}}. Not on GET or DELETE. |
parse |
How to read the response: auto (default), json, text or feed. |
fail_on_status |
Default true: a 4xx or 5xx response is a step error, so retry applies. false returns the response instead. |
With auto, the step reads JSON when the response says it is JSON, an RSS
or Atom feed when it is one, and text otherwise.
Output: status, headers (a short list: content-type, etag,
last-modified, location, retry-after and x-ratelimit-*), and body,
the parsed JSON or the text. For a feed, body is empty and items holds
a list of title, link, published, published_ts (seconds since 1970),
and summary. A feed gives at most 1,000 items, and each summary is plain
text up to 2,000 characters.
The step only reaches public web addresses; private and internal addresses
are refused. A response can be up to 5 MB, and a request times out after
30 seconds of waiting for the response. Requests other than GET carry an
Idempotency-Key header built from the run, the step and the iteration,
unless you set one, so a service that supports it can ignore a repeat.
To read only what is new since the last good run, use run.last_success_at
and run.last_success_ts; see Run facts below.
The email step
Section titled “The email step”An email step sends a plain-text email.
send: kind: email to: ["ana@example.com", "{{ vars.manager }}"] subject: "Digest for {{ run.started_at[:10] }}" body: "{{ summarise.results | map(attribute='text') | join('\n\n') }}"tois a list of 1 to 50 addresses, or a single reference to a list of addresses. Duplicates are removed.subjectcan be up to 200 characters andbodyup to 100 KB.- Recipients must belong to the workspace: a current member, or an address on a domain the workspace has verified. If any address does not qualify, the whole step fails and names it; nothing is sent.
- A run can queue at most 100 emails.
The email is queued and then sent shortly after, and a failed send is
retried. Each recipient’s email is queued once. If the platform crashes
while sending, it can be delivered twice, at most once more.
The email says it was sent by a workflow in your workspace. Output:
queued, the list of addresses.
The ingest step
Section titled “The ingest step”An ingest step adds documents to a collection. It only queues them: the
platform then fetches web addresses, parses and indexes each document in
the background, so the step finishes quickly and the documents are
searchable a little later.
To add one entry, usually inside a for_each:
add: kind: ingest collection: news url: "{{ item.link }}" metadata: published: "{{ item.published }}"To add one entry per item of a list, set items, and optionally where
to keep only some of them (the same rules as
Conditions). item and index are
available in the entry’s fields:
add: kind: ingest collection: news items: "{{ fetch.items }}" where: left: "{{ item.published_ts }}" op: gt right: "{{ run.last_success_ts }}" url: "{{ item.link }}"collectionis written out in the step, never a template.- Each entry has either a
urlorcontent(text).contentneeds afilename, whose extension chooses how it is parsed, such asnote.md. With aurl,filenameis optional and the platform works one out from the address. pathputs the document in a folder.metadatamaps field names to templates. At most 30 fields are allowed, andtitleand the other reserved metadata names cannot be set.- List mode takes at most 1,000 items after
where, and is not allowed inside afor_eachorloop. One entry’s content can be at most 1 MB. - An entry that cannot be queued (nothing to add after rendering, an
unsafe folder, metadata the collection rejects, too much content)
is counted in
failedand the step still finishes. - Output:
import_id,item_ids(the entries this step queued),queuedandfailed.
Who it runs as. The step adds documents as whoever runs the workflow, or as the trigger’s owner in a scheduled or webhook run. That person’s role and their access to the collection must allow writing to it. The check happens when the step runs, and when you save the workflow you must be able to write to the collection yourself. A refused run says only that it cannot add documents to the collection, whether or not the collection exists. Visitors chatting through an embed cannot add documents.
Quotas. The step checks your workspace’s document-count and storage
quotas, and the trial rules, before it queues anything. A run that would go
over fails with the numbers, for example document_count quota exceeded: limit 10, currently using 10, this request needs 3, and queues nothing.
Web addresses are fetched later, so each fetched page’s real size is
checked again then.
Seeing the result. Everything one step adds in one run appears as a
single import under the collection’s Recent imports, where each entry shows
as it is fetched, indexed, skipped or failed. A run with nothing new to add
opens no import, and its import_id is empty.
Running twice. Running a step again, from a retry or a resumed run, never queues an entry twice. A document identical to one already in the collection is skipped as a duplicate. A page that has changed since it was last added arrives as a new document, not as a new version of the old one.
The documents step
Section titled “The documents step”A documents step works with the documents already in a collection. Its
action says what it does: list, read or set_metadata. The
collection is written out in the step, never a template. To let a run
choose, see Several collections below.
To list the documents that match a filter:
find: kind: documents action: list collection: contracts filters: {category: {exists: false}} limit: 100filters uses the same filter object
as search, including exists, and its values may be templates. On a list,
filters may instead be one template that renders to a filter object, which
makes a filter optional: "{{ {'folders': {'any': [vars.folder]}} if vars.folder else {} }}"
filters by folder when there is one and by nothing when there is not. limit
is 100 by default and at most 1,000, and it may be a template that renders
to a whole number in that range (for example "{{ request.limit | default(1000) }}");
any other result is a step error. offset skips that many documents first
(0 by default, at most 100,000, also a template), so a large collection can
be taken in batches across runs; a page past the end is empty and reports
total 0. Only finished, active documents are
listed, oldest first. Output: documents (each with id, filename,
collection and metadata), count (how many are in this list), total
(how many match in all, so a flow can tell it saw only part) and totals
(per collection: collection, total and count).
To read one document’s text:
read: kind: documents action: read collection: contracts document: "{{ item.id }}" max_chars: 20000The text is the document’s parsed text, without the parts the collection
excludes, cut at max_chars (20,000 by default, at most 100,000). The cap
is 100,000 characters because the text usually travels in a template, such
as an agent’s input, and a template’s result is limited to 256 KB. max_chars
may be a template that renders to a whole number from 1 to 100,000.
Output: id, filename, collection, text, metadata and truncated.
To set a document’s metadata:
write: kind: documents action: set_metadata collection: contracts document: "{{ item.id }}" metadata: category: "{{ normalise.value }}"metadata maps up to 30 field names to templates. A value that comes out
empty is skipped rather than written, so a step that could not find a value
never clears one, and there is no way to clear a field from a step. The
other fields of the document are left alone. A value the collection’s
schema rejects fails the step and names the field. The new values reach
the document’s chunks without re-indexing, and a pending proposal for a
field you set is dropped. Output: id, written (the values saved) and
skipped (the fields left alone). Nothing changes when there is nothing to
write.
Several collections
Section titled “Several collections”A step can instead list the collections it may use under collections, and
let a template in collection choose among them when the workflow runs.
The list is written out in the step and checked when you save, like a
single collection. A name the template produces that is not in the list
fails the step, and nothing is read from it.
find: kind: documents action: list collections: [contracts, policies] collection: "{{ vars.request.collections }}" limit: 20
each: kind: for_each items: "{{ find.documents }}" body: steps: read: kind: documents action: read collections: [contracts, policies] collection: "{{ item.collection }}" document: "{{ item.id }}"On a list, collection may render to one name or a list of names, and an
empty result (or no collection at all) lists every collection in
collections. limit and offset apply to each collection in turn, and
the documents come back collection by collection, in the order asked. Each
listed document carries its collection, so a later step can read it from
the right one. A read or set_metadata step’s collection must render to
exactly one of the names. A literal filter is checked against every listed
collection’s schema.
Who it runs as. The step reads as whoever runs the workflow, or as the
trigger’s owner in a scheduled or webhook run, and sees only the documents
that person could open themselves. A document they cannot read is never
listed, never counted in total, and cannot be read or changed. Asking
for one by id gives the same “document not found” as asking for one that
does not exist. Setting metadata also needs write access to the collection
at the moment the step runs, and when you save the workflow you must be
able to write to it yourself. An archived document cannot be changed.
Every change is recorded in the audit log under the person who ran it.
The step’s record in run history names the fields it touched, never
their values or the document’s text.
The code step
Section titled “The code step”A code step runs a short piece of Python on the values you hand it and
returns a result for later steps.
normalise: kind: code title: Normalise the value inputs: value: "{{ extract.value }}" code: | def main(value): return {"value": value.strip() or None} outputs: [value]codemust define a top-leveldef main(...). It is called with each ofinputsas a keyword argument, and must return a dictionary that can be written as JSON. A syntax error or a missingmainis reported when you save, with the line.inputsmaps names to templates, up to 30. A name is a Python identifier. A value that is a whole reference keeps its type, so a list or a number arrives as one.outputslists the keysmainreturns that later steps may read, assteps.<name>.<key>. A reference to a key you did not list is refused at save, and a run fails ifmainleaves one out.timeout_sis 30 seconds by default and at most 300. A run that takes longer fails.printoutput is kept (the last 4 KB) and shown in the step’s record in run history, which is the place to look when a step misbehaves.
The code runs in an isolated sandbox with Python 3.12, no network, and no
access to your documents or anything else in the platform; it sees only
its inputs. Besides Python’s own library it can import dateutil,
rapidfuzz, regex, unidecode, yaml (PyYAML) and tzdata. A run uses
one sandbox for all its code steps, and each execution gets its own
scratch directory, so steps inside a for_each do not see each other’s
files. The sandbox is the same on every plan and trust tier.
A step fails with a message when the code raises (the error and the end of the traceback), when it runs out of time, when it does not return a dictionary, or when the sandbox is unavailable. A step that failed because the sandbox was unavailable can be retried like any other.
The tool step
Section titled “The tool step”A tool step calls one tool from an integration (an MCP server) that your
platform’s operators have registered.
lookup: kind: tool tool: mcp__crm__find_customer args: {email: "{{ input.body.email }}"}tool is the full tool name, which starts with mcp__, and args maps
argument names to templates. The tool runs under the same
trust tier rules as an agent’s tool calls: if the
workflow’s tier does not allow the tool, saving is refused, and a run that
finds it denied fails. The search tool is not available here; use a
retrieve step. Output: the tool’s result, as JSON when it is JSON and text
otherwise, up to 256 KB.
A step cannot call a tool whose server is set to always ask; call it from an agent in the workflow instead. A tool call that fails, because the server refused it or could not be reached, fails the step.
Run facts
Section titled “Run facts”In a triggered run, a workflow’s templates can read:
| name | what it holds |
|---|---|
input.body |
The webhook’s body, or empty for a schedule. |
input.message |
The trigger’s input text, filled in. |
run.trigger_kind, run.trigger_name |
schedule or webhook, and the trigger’s name. |
run.last_success_at, run.last_success_ts |
When the trigger last succeeded, as a date and as seconds since 1970. |
Before a trigger’s first success, last_success_at is, for a schedule,
the scheduled time before this run’s own, so a first run covers one
interval rather than a feed’s whole history. For a webhook it is the time
the trigger was created.
Outside a triggered run these are empty, and last_success_ts is 0.
For an agent, there are no templates in the agent itself. The trigger’s input text is filled in first, and the result is the run’s first message.
Scheduled digest
Section titled “Scheduled digest”The Scheduled digest template, in the New workflow dialog, builds a working example of all of the above. Every weekday it reads a feed, summarises what is new, and mails the summary to your team.
You provide:
- Feed URL, an RSS or Atom address.
- Recipients, up to 50 addresses. Each must be a member of your workspace or an address on a domain your workspace has verified. This is checked when you create the digest and again each time it runs.
- Time, default 08:00.
- Time zone, defaulting to your browser’s.
It creates a workflow that reads the feed (an http step with
parse: feed), summarises each item that is new since the last successful
run with a for_each and one llm step, composes one digest, and emails it.
It also creates a weekday schedule trigger at your time, owned by you.
There are no agents to maintain.
What counts as new:
- An item is new when its date is later than the last successful run. On the trigger’s first run, that means since the previous scheduled time.
- The digest covers at most the feed’s first 20 entries that are new. It assumes the feed lists its newest entries first, as most feeds do.
- Entries without a date never count as new.
Each item is summarised in two sentences with its title and link, and the
digest keeps each item’s title and link. The subject is
Digest for followed by the date (UTC) the run started.
On a day with no new items it still sends a short “Nothing new” email, so a quiet day is distinguishable from a broken trigger. To change anything, edit the workflow or the trigger afterwards like any other.
Scheduled ingestion
Section titled “Scheduled ingestion”The Scheduled ingestion template, in the New workflow dialog, keeps a collection up to date with a feed. Every day it reads the feed and adds the entries published since the last time it worked.
You provide:
- Feed URL, an RSS or Atom address.
- Collection, one you can write to.
- Time, and Time zone, defaulting to your browser’s.
It creates a workflow with two steps, an http step that reads the feed and an ingest step in list mode, plus a daily schedule trigger at your time, owned by you. There are no agents to maintain.
What gets added:
- An entry is added when its date is later than the last successful run. On the trigger’s first run, that means since the previous scheduled time.
- At most 50 entries are added in a run. It assumes the feed lists its newest entries first.
- Entries without a date are never added.
- Each entry is fetched from its link, and the document’s date is kept in
a
publishedmetadata field.
The documents are fetched, parsed and indexed after the run, as one import under the collection’s Recent imports. To also add documents when another system calls a web address, open the workflow’s Triggers tab and add a webhook trigger; it can run beside the schedule or replace it. Remove the schedule if you do not want the daily run.
If the collection is over its quota, the run fails and names the quota. A page whose content changes under the same address is added again as a new document, not as a new version.
Batch document processing
Section titled “Batch document processing”The Batch document processing template, in the New workflow dialog, fills in one metadata field across a collection. It finds the documents that do not have the field yet, reads each one, asks a model for the value, tidies the answer with a bit of Python, and writes it back.
You provide:
- Collection, one you can write to.
- Field to fill, one of that collection’s declared fields. Its type and, for a choice, its options shape what the workflow asks for and how the answer is tidied.
It creates a workflow with these steps and no agents:
- A documents step lists up to 100 documents whose
field is missing (
exists: false). - A
for_each, three at a time, runs the rest for each of them. - A documents step reads the document, up to 20,000 characters.
- An llm step asks for the field’s value, named with its description and options, and to answer with nothing when the document does not say.
- A code step turns the answer into a proper value for
the field’s type. A choice becomes the closest option, a date becomes
YYYY-MM-DD, a number accepts a decimal comma, a yes/no is read from words such as yes, no, true and false, tags are split and de-duplicated, and text has its spacing tidied. An empty or unreadable answer becomes no value. - A documents step writes the value back. With no value, the document is left alone.
- A reply says how many documents were checked and how many were filled.
There is no schedule. Run it from the chat or the API, then open the workflow’s Triggers tab to add one, for example a nightly run that picks up whatever is new. Each run handles the first 100 documents still missing the field, so a large collection takes several runs, and a document the model could not fill is looked at again next time.
The run touches only what its owner can read and write: documents they cannot open are not listed and not changed. A viewer of the collection who runs the workflow gets a refusal when it reaches the write. Each write appears in the audit log, and the model calls are billed like any other llm step. To change anything, edit the workflow afterwards like any other.
Ticket triage
Section titled “Ticket triage”The Ticket triage template, in the New workflow dialog, answers support tickets sent by another system, with a person approving each reply.
You provide:
- Collection, the knowledge the replies draw on.
- Approvers, workspace members with verified emails. You cannot be the only approver, because the person who creates the trigger owns its runs and cannot approve them.
- Reply URL, where approved replies and the outcome of rejected or expired tickets are posted.
It creates the workflow and a webhook trigger named “Incoming tickets”. The webhook URL is shown once, when the template is created; copy it then. Send each ticket as JSON:
{"id": "T-1042", "subject": "Refund window", "body": "How long do I have?", "requester": "sam@example.com"}For each ticket the workflow:
- Decides with a router whether it is a support question. Anything else ends the run.
- Has a drafter agent search the collection and write a reply.
- Pauses on an approval step. The approvers are mailed; the message shows the ticket, and the drafted reply is editable. Requests expire after 3 days.
- On approval, posts
{"ticket": "<id>", "reply": "<text>"}to the Reply URL, using the approver’s edited text. A failed post fails the run. - On rejection or expiry, posts
{"ticket": "<id>", "status": "rejected"}(or"timed_out") to the Reply URL, so the ticketing system always learns the outcome and can notify the customer itself. It then mails the requester that a colleague will get back to them. The requester must be a workspace member or on a verified domain; otherwise that mail fails.
Approvers decide on the Approvals page.
The ticket’s text reaches approvers in the mail and on the Approvals page, so approvers should treat it as outside input.
Limits
Section titled “Limits”| Triggers per workspace | 50 |
| Shortest schedule interval | 15 minutes |
| Webhook body, rate, waiting deliveries | 1 MB, 60 per minute, 100 |
| Failed runs in a row before a trigger pauses | 3 |
| notify addresses | 20 |
| for_each items, concurrency | 1,000, 1 to 10 |
| loop iterations | 50 |
| Container nesting | one level |
| http response, headers per step | 5 MB, 30 |
| Feed items | 1,000 |
| Email recipients per step, emails per run | 50, 100 |
| Ingest items per step, one entry’s content, metadata fields | 1,000, 1 MB, 30 |
| Documents listed per step, text read, metadata fields | 1,000, 100,000 characters, 30 |
| Code step timeout, code size, inputs | 300 s, 64 KB, 30 |
| Secrets per workspace, one secret’s size | 100, 8 KB |
