API Reference

Base URL: https://willowriverautomation.com/relay/v1

Set this in your shell for the examples below:

export BASE="https://willowriverautomation.com/relay/v1"

All responses are JSON. Errors:

{ "error": { "code": "string", "message": "string", "details": {} } }

Authentication & account

Method Path Auth Description
POST /auth/signup Public Create account (sends verification email)
POST /auth/verify Public Verify email with one-time token from email
POST /auth/resend-code Public Resend verification token
POST /auth/forgot-password Public Request password reset (uniform response)
POST /auth/reset-password Public Reset password with token from email
POST /auth/api-key Public (email+password) Mint API key (shown once)
GET /account API key Org profile, plan, usage
DELETE /account API key + email/password Delete account and org data
GET /account/audit-log API key Org audit trail (paginated)
PATCH /account/billing API key Toggle allow_overage
POST /billing/checkout API key Stripe Checkout URL (starter or growth)
POST /billing/portal API key Stripe Customer Portal URL
GET /billing/usage API key Usage snapshot + overage rate

Plan limits and enforcement

Limits match the Intake Relay pricing page. Full breakdown: billing & quotas.

Limit Free Starter Growth Scale
Intake submissions / period 10 2,000 20,000 Custom
Flows 1 5 Unlimited Unlimited
Webhook endpoints (active) 1 3 10 Custom
Version pinning (?version=N) Yes Yes Yes Yes
Webhook retries + delivery log Yes Yes Yes Yes
File uploads No Yes Yes Yes
File upload size (per file) No 10 MB 10 MB By request
Uploaded file retention No 60 days 60 days 60 days

Usage snapshot (GET /account)

{
  "plan": "starter",
  "plan_status": "active",
  "allow_overage": false,
  "usage_period_start": "2026-05-01T00:00:00+00:00",
  "usage_period_end": "2026-06-01T00:00:00+00:00",
  "submissions": { "used": 42, "limit": 2000, "remaining": 1958 },
  "flows": { "used": 2, "limit": 5 },
  "webhooks": { "used": 1, "limit": 3 }
}

limit: null means unlimited (Growth/Scale flows, Scale submissions/webhooks). Starter and Growth can opt in to submission overage via PATCH /account/billing.

Version pinning

Intake submit and file presign require ?version=N pointing at a published flow version unless the flow explicitly allows latest intake. Missing version returns version_required (400). See Intake flows.

Webhook retries and delivery log

All plans include automatic retries (5 attempts, exponential backoff) and GET /flows/{flow_id}/webhooks/deliveries. See Webhooks.

Authentication (requests)

Management APIAuthorization: Bearer <api_key>

Public intakeX-Embed-Key: <embed_key> (per-flow, safe for frontend)

Intake flows

Method Path Auth Description
POST /flows API key Create intake flow
GET /flows API key List flows
GET /flows/{flow_id} API key Get flow metadata
POST /flows/{flow_id}/rotate-embed-key API key Rotate embed key

Flow versions

Method Path Auth Description
POST /flows/{flow_id}/versions API key Create draft version
PUT /flows/{flow_id}/versions/{v} API key Update draft
POST /flows/{flow_id}/versions/{v}/publish API key Publish (immutable)
GET /flows/{flow_id}/versions/{v} API key or embed key Fetch schema

Intake submissions

Method Path Auth Description
POST /flows/{flow_id}/intake?version=N API key or embed key Submit intake
GET /intake/{id} API key Get submission
GET /flows/{flow_id}/intake API key List submissions
GET /intake/{id}/events API key Event log for submission

Submit intake

curl -X POST "$BASE/flows/$FLOW_ID/intake?version=1" \
  -H "X-Embed-Key: $EMBED_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: user-abc-signup" \
  -d '{"data":{"email":"dev@acme.com","plan":"pro"}}'

Idempotency: Pass Idempotency-Key header. Duplicate keys return the original submission with idempotent_replay: true.

File uploads

Method Path Auth Description
POST /flows/{flow_id}/uploads/presign API key or embed key Get presigned upload URL

Plan requirement: File uploads require Starter, Growth, or Scale. Free accounts receive uploads_not_available (403) if presign or intake submission includes file attachments.

Pass ?version=N matching your pinned published flow version. The field_id must refer to a file type field on that version.

On paid plans, presigned uploads are capped at 10 MB per file (UPLOAD_MAX_BYTES). The blob must exist in GCS before intake submission accepts the file reference. Files are stored in Google Cloud Storage under your organization prefix. Uploaded objects are automatically deleted after 60 days via bucket lifecycle policy — Intake Relay is not long-term file archival. Download or copy files to your own systems via webhooks if you need longer retention.

Scale plan: Larger per-file limits (for example video) are available by special request only, subject to custom pricing and manual configuration. Contact Willow River Automation before building flows that depend on higher limits.

Presign example

curl -X POST "$BASE/flows/$FLOW_ID/uploads/presign?version=1" \
  -H "X-Embed-Key: $EMBED_KEY" \
  -H "Content-Type: application/json" \
  -d '{"field_id":"resume","filename":"cv.pdf","content_type":"application/pdf"}'

Success (200):

{
  "storage_key": "uploads/{org_id}/{flow_id}/{uuid}/cv.pdf",
  "upload_url": "https://storage.googleapis.com/...",
  "expires_at": "2026-05-21T12:00:00Z"
}

Free plan (403):

{
  "error": {
    "code": "uploads_not_available",
    "message": "File uploads are not available on the free plan. Upgrade to Starter or Growth.",
    "details": { "plan": "free", "...": "..." }
  }
}

Webhooks

Method Path Auth Description
POST /flows/{flow_id}/webhooks API key Register endpoint
GET /flows/{flow_id}/webhooks API key List endpoints
DELETE /flows/{flow_id}/webhooks/{id} API key Deactivate endpoint
GET /flows/{flow_id}/webhooks/deliveries API key Delivery log

System

Method Path Description
GET /health Health check
GET /events Registered workflow event types

Common errors

Code HTTP Meaning
unauthorized 401 Missing or invalid credentials
not_found 404 Resource not found
validation_error 400 Missing required request field
validation_failed 400 Intake data failed schema validation
version_required 400 Must pass ?version=N
immutable_version 409 Cannot edit published version
duplicate_slug 409 Flow slug already exists in org
uploads_not_available 403 File uploads require Starter, Growth, or Scale (not on Free)
quota_exceeded 429 Plan limit reached: submissions, flows, or webhook endpoints
billing_inactive 402 Subscription past due or canceled