An intake flow is a versioned JSON schema that defines structured data collection for SaaS onboarding, signup wizards, KYC steps, or any multi-step intake.
Define flows through the hosted API at https://willowriverautomation.com/relay/v1. See the API reference for endpoints.
This is not a form builder UI. You define flows via API.
{
"fields": [
{
"id": "email",
"type": "email",
"label": "Work email",
"validation": { "required": true }
},
{
"id": "company",
"type": "text",
"label": "Company name"
},
{
"id": "plan",
"type": "select",
"label": "Plan",
"options": ["starter", "pro", "enterprise"],
"logic": {
"visibleWhen": { "field": "company", "op": "isNotEmpty" },
"requiredWhen": { "field": "company", "op": "isNotEmpty" }
}
},
{
"id": "mrr_estimate",
"type": "computed",
"expression": "5000 if plan == 'pro' else 500"
}
],
"steps": [
{ "id": "account", "title": "Your account", "fieldIds": ["email", "company"] },
{ "id": "plan", "title": "Choose plan", "fieldIds": ["plan"] }
],
"variables": {
"region": { "default": "us" }
},
"settings": {
"maxFileSizeMb": 10
}
}
| Type | Description |
|---|---|
text, email, textarea |
String inputs |
number |
Numeric input |
select, radio |
Option lists |
checkbox |
Boolean |
file |
Upload via presigned URL |
computed |
Server-evaluated expression |
date, hidden |
Supported in validation layer |
Logic uses a JSON DSL evaluated server-side (source of truth) and client-side (UX):
{ "field": "company", "op": "isNotEmpty" }
{ "and": [ { "field": "plan", "op": "eq", "value": "pro" } ] }
Operators: eq, neq, isEmpty, isNotEmpty, gt, gte, lt, lte, in, contains
POST /flows/{id}/versions → draft v1
PUT /flows/{id}/versions/1 → edit draft
POST /flows/{id}/versions/1/publish → immutable v1
POST /flows/{id}/versions → draft v2
POST /flows/{id}/versions/2/publish → immutable v2
Embeds using data-version="1" continue working after v2 is published.
Typical onboarding flow fields:
Each step maps to downstream workflow actions via webhooks on intake.submitted.
On intake submit:
IntakeSubmissionintake.submittedValidation errors return field-level details:
{
"error": {
"code": "validation_failed",
"message": "Submission validation failed",
"details": { "email": ["This field is required"] }
}
}