Skip to content

Triggers and automation

This content is not available in your language yet.

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.

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.

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.

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, with 413, 415 or 400, 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.

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.

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.

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.

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.com allows every subdomain but not example.com itself, 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.

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.

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.

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.

  • 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.

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.

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') }}"
  • to is a list of 1 to 50 addresses, or a single reference to a list of addresses. Duplicates are removed.
  • subject can be up to 200 characters and body up 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.

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 }}"
  • collection is written out in the step, never a template.
  • Each entry has either a url or content (text). content needs a filename, whose extension chooses how it is parsed, such as note.md. With a url, filename is optional and the platform works one out from the address.
  • path puts the document in a folder. metadata maps field names to templates. At most 30 fields are allowed, and title and the other reserved metadata names cannot be set.
  • List mode takes at most 1,000 items after where, and is not allowed inside a for_each or loop. 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 failed and the step still finishes.
  • Output: import_id, item_ids (the entries this step queued), queued and failed.

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.

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: 100

filters 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: 20000

The 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.

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.

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]
  • code must define a top-level def main(...). It is called with each of inputs as a keyword argument, and must return a dictionary that can be written as JSON. A syntax error or a missing main is reported when you save, with the line.
  • inputs maps 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.
  • outputs lists the keys main returns that later steps may read, as steps.<name>.<key>. A reference to a key you did not list is refused at save, and a run fails if main leaves one out.
  • timeout_s is 30 seconds by default and at most 300. A run that takes longer fails.
  • print output 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.

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.

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.

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.

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 published metadata 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.

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:

  1. A documents step lists up to 100 documents whose field is missing (exists: false).
  2. A for_each, three at a time, runs the rest for each of them.
  3. A documents step reads the document, up to 20,000 characters.
  4. 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.
  5. 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.
  6. A documents step writes the value back. With no value, the document is left alone.
  7. 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.

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:

  1. Decides with a router whether it is a support question. Anything else ends the run.
  2. Has a drafter agent search the collection and write a reply.
  3. 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.
  4. On approval, posts {"ticket": "<id>", "reply": "<text>"} to the Reply URL, using the approver’s edited text. A failed post fails the run.
  5. 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.

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