# Flows
Source: https://sovaria.in/docs/flows

Automations without servers: when a webhook, schedule, table or collection change, queue message or form fires, run steps — HTTP calls, functions, database writes, email, AI — with branches, loops, waits, retries and a full run history.

A **flow** is a graph of steps. One or more **triggers** start it; each step gets the items the step before it sent, does its work, and passes items on. Flows run alongside your functions: every run is durable (a crash or restart resumes it), every step is recorded with its input, output and error, and a failed run can be retried from the step that failed.

(Diagram: A flow run)

## Triggers and steps

| Kind | Steps |
|---|---|
| Triggers | Manual (Test run), **Webhook**, **Schedule**, **Sovaria event** (table rows, documents, auth users, storage objects, realtime messages), **Queue message**, **Form**, Sub-flow, Error flow; app triggers poll Google Drive, Sheets, Gmail, Calendar and Telegram. |
| Flow control | **If**, Switch, Filter, Merge, **Loop over items**, **Wait** (time or a signal), Approval, Sub-flow, Respond to webhook, Stop with error. |
| Data | **Edit fields** (set / rename / keep), Code (JavaScript or Python in the sandbox), Sort, Limit, Remove duplicates, Aggregate, Summarize, Split out, Date & time, Crypto, HTML extract, Convert, Compare. |
| Sovaria | SQL, **Table rows** (get / insert / update / upsert / delete), Documents, Storage, **Send message** (email, SMS, push, in-app), Realtime publish, **Run a function**, Send to a queue, Analytics event. |
| AI | **AI** (complete, classify, extract, summarise), Agent run, Knowledge base search — billed as Sovariq credits. |
| Network and apps | **HTTP Request** (any public API, with pagination), and 13 apps (Google Drive, Sheets, Gmail and Calendar; Microsoft Outlook, OneDrive and Excel; Slack, Notion, GitHub, HubSpot, Airtable, Stripe, Twilio, Discord, Telegram, OpenAI, Anthropic) plus generic OAuth2, API-key, bearer, Basic and header auth, through [Integrations](https://sovaria.in/docs/integrations) connections. Apps that sign in with OAuth use Sovaria's app where staff have set it up, or your project's own OAuth app (**Connections › Use your own OAuth app**, which shows the redirect URL to register); the Connections page says which is available. |

Any text setting can hold an **expression**: `{{ $json.email.toLowerCase() }}` reads the current item, `{{ $('Shape').item.json.total }}` reads another step, `$now`, `$execution.id` and `$vars` are there too. Expressions are evaluated safely (no code runs); use a Code step for logic.

## Build one in the console

1. **Functions › Flows › New flow** — Start blank or from a template (Stripe payment → row + email, form → Slack, nightly report…).
2. **Pick a trigger, then add steps** — Click **+** on a step’s output to add the next one, or drag from an output handle onto a step. Hover or click a connection for **+** (add a step in between; later steps move right) and the bin (remove it); Delete removes a selected connection. From the keyboard: select a step, press **]** or **[** to move through its connections, or use the Connections list in the step’s settings. Click a step to set it up; settings with `{{ }}` take expressions.
3. **Test run** — Run the draft once with sample data (JSON), or **listen** for one real request on the webhook’s test URL. Each step shows the items it got and sent; click a step to see its input and output.
4. **Publish, then turn it on** — Edits save as a draft. **Publish** makes a version; the live flow runs only the published version, and only while it’s **on**. **Versions** lets you put an older version back in the draft.

## Webhooks are always protected

A webhook URL looks like `https://<project-ref>.sovaria.in/hooks/flows/<flow-id>/<path>?token=<flow token>`. Every webhook checks the caller one of four ways:

| Authentication | The caller sends |
|---|---|
| **URL token** (default) | The full URL from the editor (it ends in `?token=…`), or the token in an `X-Flow-Token` header. Sovaria makes one token per flow; **New token** in the step panel (or `sov flows rotate-token`) replaces it and old URLs get 401 at once. |
| Header value | A header (default `X-Webhook-Token`) equal to a project secret. |
| Basic auth | `Authorization: Basic …` matching a project secret `user:password`. |
| HMAC signature | A signature with a project secret in the sender's own format. **Signature format**: Stripe (`Stripe-Signature: t=…,v1=…`), Slack (`X-Slack-Signature` with `X-Slack-Request-Timestamp`), GitHub (`X-Hub-Signature-256: sha256=…`), Shopify (`X-Shopify-Hmac-Sha256`, base64), Standard Webhooks (`webhook-id`, `webhook-timestamp`, `webhook-signature`: Svix, Resend, Clerk), or a plain HMAC-SHA256 of the body as hex or base64. **Detect from the header** (the default) picks the format from the header name. |

- Stripe, Slack and Standard Webhooks sign a timestamp too: a request more than 5 minutes old (change it with **Timestamp tolerance**, 30 seconds to 1 hour) is refused. A second delivery of a request already accepted (a sender's retry, GitHub's **Redeliver**) is answered `200 {"ok": true, "duplicate": true}` without running the flow again; GitHub and Shopify sign no timestamp, so their repeats are matched by delivery id and body: that catches duplicate deliveries, but a captured request replayed with a new delivery id isn't caught. Keep their URLs and secrets private.
- Signatures are compared in constant time. Put the provider's signing secret in a project secret (for Stripe the endpoint's `whsec_…`, for Slack the app's **Signing Secret**) and name it in **Secret**.
- Slack's Events API checks a new URL with a signed `url_verification` request: the webhook answers its challenge itself and the flow doesn't run. The **Slack event → database row** template is set up for this.

> **Note:** The token never reaches your flow’s data: it is removed from the item’s `query` and headers, and it is not stored in the run. Prefer the header when a caller can set one (URLs end up in other systems’ logs).

## Runs, errors and retries

- **Run history** (Runs in the editor, `sov flows runs`): every execution with its mode (test run, webhook, schedule, event…), version, time and status; open one to see each step’s items, time and error.
- **When a step fails** it stops the run by default. Per step you can instead *continue* with the error as the item, send the error to an extra **error output**, and **retry** up to 10 times with a wait between tries.
- **Retry a run** from the step that failed — it gets the same input the failed step had — or from the start on the latest published version. Cancel stops a run that is still going.
- **Error flow**: in the flow’s settings, name another flow to run whenever this one fails (it starts with an Error trigger).
- **Secrets never show in runs.** Any project secret or connection token a step used is replaced by `[secret]` in that step’s output, errors and webhook responses, even when an API echoes it back.

## Security

- HTTP Request steps reach **public addresses only**: URLs are checked (no IP literals, no localhost or internal names) and calls go through the same egress proxy as functions, which refuses private, loopback and link-local addresses on every redirect.
- Steps that touch your data (SQL, rows, documents, storage, messages) call the Sovaria API **as the project’s service role**: your access rules for the service role, plan limits, metering and the audit log all apply.
- Flows belong to one project and environment; nothing crosses projects. Editing and publishing need **functions.deploy**; test runs, retries and approvals need **functions.invoke**; viewing needs **functions.read**.
- Every run is capped: steps per run (loops count every round), items per step, data per run and the longest wait — see the limits below. A runaway loop stops with `PLAN_LIMIT_REACHED`.

## Plan limits

|  | Free | Pro | Ultra | Team | Enterprise |
|---|---|---|---|---|---|
| Flows per project (flow.flows) | 5 | 50 | 200 | 500 | 2,000 |
| Flow runs per month (flow.executions_month) | 1,000 | 25,000 | 100,000 | 500,000 | 2,000,000 |
| Steps per run, every loop round counted (flow.nodes_per_run) | 200 | 2,000 | 5,000 | 10,000 | 20,000 |
| Shortest gap between scheduled runs (flow.schedule_min_minutes) | 15 min | 5 min | 1 min | 1 min | 1 min |
| Highest form submissions a minute per visitor a flow may allow (flow.form_per_visitor_per_min) | 30 | 120 | 300 | 600 | 1,000 |
| Unfinished runs per project (flow.executions_active) | 20 | 1,000 | 5,000 | 20,000 | 100,000 |
| Run history kept (flow.history_days) | 1 day | 7 days | 14 days | 30 days | 30 days |
| Longest Wait or Approval (flow.wait_days) | 1 day | 7 days | 30 days | 90 days | 365 days |
| Items one step may output (flow.items_per_node) | 2,000 | 20,000 | 50,000 | 100,000 | 100,000 |
| Data one run may hold (flow.execution_bytes) | 4 MB | 16 MB | 32 MB | 64 MB | 64 MB |

Runs per month count executions started by triggers (webhooks, forms, schedules, events, queues, app polling). **Test runs, retries and sub-flow calls don’t count.** A redelivered event starts nothing new and isn’t counted twice. When the month’s runs are used up, triggers are refused with `PLAN_LIMIT_REACHED` and the flow’s run history shows each refused run. Schedules faster than your plan allows are refused when you turn the flow on; app triggers poll no faster than it. A public form takes at most 20 submissions a minute from one visitor (an IPv6 /64 counts as one) and the flow’s webhook rate limit overall. A kiosk or an event check-in where everyone shares one network can raise the per-visitor cap in the flow’s settings (**Form submissions per visitor a minute**, `form_per_visitor_per_min`, up to your plan’s `flow.form_per_visitor_per_min`; after a downgrade the plan’s value applies); a webhook that answers after the flow ran waits up to 25 seconds, or answers `202` with the execution id at once when Sovaria is already waiting on many such calls.

## From code and the CLI

CLI:
```bash
sov flows create "New signup" --template stripe-paid
sov flows pull flw_123 > flow.json     # edit the graph, then
sov flows push flw_123 flow.json
sov flows test flw_123 --data '{"email": "ada@example.com"}'
sov flows publish flw_123 --note "first version" && sov flows on flw_123
sov flows runs flw_123 --status error
sov flows retry fex_456                # from the failed step
sov flows rotate-token flw_123         # new webhook URL token
```

JavaScript:
```js
const { flows } = await sov.api.flows.list()
const run = await sov.api.flows.test({ flowId: 'flw_123', triggerItems: [{ json: { email: 'ada@example.com' } }] })
const execution = await sov.api.flows.execution({ executionId: run.execution_id })
await sov.api.flows.retry({ executionId: execution.id, fromNode: execution.error?.node_id })
```

Python:
```python
flows = await client.api.flows.list()
run = await client.api.flows.test("flw_123", trigger_items=[{"json": {"email": "ada@example.com"}}])
execution = await client.api.flows.execution(run["execution_id"])
await client.api.flows.rotate_webhook_token("flw_123")
```

> **Tip:** Sovariq can build flows for you: ask “when someone signs up, add them to the newsletter table and send a welcome email” in the builder; it drafts the flow, test-runs it with sample data and leaves publishing to you.
