Intake Flows

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.

Schema structure

{
  "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
  }
}

Field types

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

Conditional logic

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

Version lifecycle

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.

SaaS onboarding example

Typical onboarding flow fields:

Each step maps to downstream workflow actions via webhooks on intake.submitted.

Validation

On intake submit:

  1. Resolve pinned flow version
  2. Evaluate conditionals → determine visible/required fields
  3. Validate types and rules
  4. Evaluate computed fields
  5. Persist IntakeSubmission
  6. Emit intake.submitted

Validation errors return field-level details:

{
  "error": {
    "code": "validation_failed",
    "message": "Submission validation failed",
    "details": { "email": ["This field is required"] }
  }
}