# Rynko Flow — Gate Schema Reference for AI/LLM > **Purpose**: Authoritative reference for AI/LLM systems creating and configuring Flow validation gates via API or MCP. > **Last Updated**: March 14, 2026 | **Version**: 1.0 --- ## QUICK START — READ THIS FIRST ### What is Rynko Flow? Rynko Flow is an AI output validation gateway. Agents submit payloads to a **Gate**; Flow validates them against a schema and expression-based business rules, routes edge cases to human approvers, and delivers results via webhook. **Core concept**: Define a Gate (schema + rules) → Submit payloads → Get structured validation results → Optionally route to human review → Deliver via webhook. ### MCP Tool Orchestration **Creating a gate and submitting payloads:** 1. `list_workspaces` — Discover available workspaces 2. `list_flow_gates` — Check existing gates 3. Create gate via REST API with schema + rules 4. `validate_{gate_slug}` — Submit payload to gate (dynamic tool per gate) 5. `get_flow_run_status` — Check run result 6. `verify_validation` — Verify payload wasn't tampered with **Submitting to an existing gate:** 1. `list_flow_gates` — Find the gate 2. `get_flow_gate` — Get gate details (schema, rules) 3. `validate_{gate_slug}` — Submit payload 4. Handle validation errors → fix payload → resubmit (self-correction chain) **Converting a Pydantic or Zod schema to a gate:** 1. `convert_schema` — Convert JSON Schema from `model_json_schema()` or `zodToJsonSchema()` to Flow ObjectSchema 2. Use the returned `schema` and `businessRules` to create a gate via REST API ### Intent-to-Action Mapping | User Says | Action | |-----------|--------| | "Create a gate for invoice validation" | Create gate with invoice schema + rules | | "Validate this payload" | Submit to appropriate gate via `validate_{slug}` | | "Check if my submission passed" | `get_flow_run_status` with run ID | | "Show me my gates" | `list_flow_gates` | | "What went wrong with this run?" | `get_flow_run_status` → inspect validation errors | | "Convert my Pydantic model to a gate schema" | `convert_schema` with format "pydantic" | | "Import my Zod schema as a gate" | `convert_schema` with format "zod" | --- ## GATE SCHEMA FORMAT ### ObjectSchema Structure Gates use an ObjectSchema format to define the expected payload structure: ```json { "type": "object", "properties": { "fieldName": { "type": "string", "required": true, "description": "Human-readable description", "minLength": 1, "maxLength": 255 } } } ``` The schema is always a top-level object with `type: "object"` and a `properties` map where each key is a field name and the value is an `ObjectPropertyDefinition`. ### ObjectPropertyDefinition Each property in the schema supports these fields: ``` type REQUIRED "string" | "number" | "boolean" | "date" | "array" | "object" required optional boolean (default: false) defaultValue optional any — default value if field is omitted description optional string — human-readable description ``` **String-specific constraints:** ``` minLength optional number — minimum string length maxLength optional number — maximum string length format optional "email" | "url" | "uuid" | "date" | "datetime" | "phone" pattern optional string — regex pattern the value must match allowedValues optional string[] — whitelist of allowed values ``` **Number-specific constraints:** ``` min optional number — minimum value (inclusive: value >= min) max optional number — maximum value (inclusive: value <= max) exclusiveMin optional number — exclusive minimum (value > exclusiveMin) exclusiveMax optional number — exclusive maximum (value < exclusiveMax) multipleOf optional number — value must be evenly divisible by this allowedValues optional number[] — whitelist of allowed values ``` **Boolean:** No additional constraints beyond `required` and `allowedValues`. **Date:** Accepts ISO 8601 date strings. Supports `format` with date-specific presets. **Array-specific constraints:** ``` itemType optional "string" | "number" | "boolean" | "date" | "object" | "array" schema optional object — nested schema for array items (when itemType is "object") ``` **Object-specific constraints:** ``` properties optional object — nested property definitions (same format as top-level) ``` **Calculated fields:** ``` isCalculated optional boolean — marks field as computed (not user-supplied) expression optional string — expression to compute value ``` ### Nesting Objects and arrays can be nested up to **5 levels deep**. Nested object properties use the same `ObjectPropertyDefinition` format recursively. ```json { "type": "object", "properties": { "customer": { "type": "object", "required": true, "properties": { "name": { "type": "string", "required": true }, "address": { "type": "object", "properties": { "city": { "type": "string" }, "zipCode": { "type": "string", "pattern": "^[0-9]{5}$" } } } } }, "lineItems": { "type": "array", "itemType": "object", "schema": { "type": "object", "properties": { "sku": { "type": "string", "required": true }, "quantity": { "type": "number", "min": 1 }, "unitPrice": { "type": "number", "min": 0.01 } } } } } } ``` --- ## VARIABLE TYPES — DETAILED REFERENCE ### string Validates that the value is a JavaScript string. | Constraint | Type | Example | Validation | |------------|------|---------|------------| | `minLength` | number | `3` | String length >= minLength | | `maxLength` | number | `255` | String length <= maxLength | | `format` | string | `"email"` | Matches format validator | | `pattern` | string | `"^INV-[0-9]{6}$"` | Matches regex pattern | | `allowedValues` | string[] | `["USD","EUR","GBP"]` | Value must be in list | **Supported formats:** - `email` — Valid email address - `url` — Valid URL - `uuid` — Valid UUID (any version) - `date` — ISO 8601 date - `datetime` — ISO 8601 date-time - `phone` — Phone number format ```json { "email": { "type": "string", "required": true, "format": "email" }, "invoiceNumber": { "type": "string", "pattern": "^INV-[0-9]{6}$" }, "currency": { "type": "string", "allowedValues": ["USD", "EUR", "GBP"] } } ``` ### number Validates that the value is a JavaScript number (not NaN, not Infinity). | Constraint | Type | Example | Validation | |------------|------|---------|------------| | `min` | number | `0` | value >= min | | `max` | number | `1000000` | value <= max | | `exclusiveMin` | number | `0` | value > exclusiveMin | | `exclusiveMax` | number | `100` | value < exclusiveMax | | `multipleOf` | number | `0.01` | value % multipleOf === 0 | | `allowedValues` | number[] | `[10, 25, 50, 100]` | Value must be in list | ```json { "amount": { "type": "number", "required": true, "min": 0, "max": 1000000 }, "percentage": { "type": "number", "exclusiveMin": 0, "exclusiveMax": 100 }, "price": { "type": "number", "min": 0.01, "multipleOf": 0.01 } } ``` ### boolean Validates that the value is `true` or `false`. ```json { "isVerified": { "type": "boolean", "required": true }, "optIn": { "type": "boolean", "defaultValue": false } } ``` ### date Validates that the value is a valid date (ISO 8601 string or Date object). ```json { "orderDate": { "type": "date", "required": true }, "expiryDate": { "type": "date" } } ``` ### array Validates that the value is a JavaScript array. When `itemType` is specified, each element is validated against the item type constraints. ```json { "tags": { "type": "array", "itemType": "string" }, "scores": { "type": "array", "itemType": "number" }, "items": { "type": "array", "itemType": "object", "schema": { "type": "object", "properties": { "name": { "type": "string", "required": true }, "quantity": { "type": "number", "min": 1 } } } } } ``` ### object Validates that the value is a plain JavaScript object (not null, not an array). ```json { "vendor": { "type": "object", "required": true, "properties": { "name": { "type": "string", "required": true }, "registrationId": { "type": "string" }, "contact": { "type": "object", "properties": { "email": { "type": "string", "format": "email" }, "phone": { "type": "string", "format": "phone" } } } } } } ``` --- ## BUSINESS RULES Business rules are cross-field validation expressions evaluated after schema validation passes. They enable complex validation logic that schema constraints alone cannot express. ### Rule Structure ```json { "id": "rule_unique_id", "name": "Human-readable rule name", "expression": "amount > 0 && amount <= budget", "errorMessage": "Amount must be positive and within budget", "enabled": true } ``` | Field | Type | Required | Constraints | |-------|------|----------|-------------| | `id` | string | Yes | Unique within the gate | | `name` | string | Yes | Max 255 characters | | `expression` | string | Yes | Max 500 characters, must pass security validation | | `errorMessage` | string | Yes | Max 500 characters | | `enabled` | boolean | Yes | Set to `false` to skip without deleting | ### Limits - **Maximum 20 rules** per gate - **Maximum 500 characters** per expression - All enabled rules are evaluated independently (no short-circuit) - A rule fails when its expression evaluates to a falsy value ### Rule Examples ```json [ { "id": "rule_positive_amount", "name": "Amount must be positive", "expression": "amount > 0", "errorMessage": "Amount must be greater than zero", "enabled": true }, { "id": "rule_date_order", "name": "End date after start date", "expression": "date_after(endDate, startDate)", "errorMessage": "End date must be after start date", "enabled": true }, { "id": "rule_total_check", "name": "Total matches line items", "expression": "total == sum(lineItems, 'subtotal')", "errorMessage": "Total does not match sum of line item subtotals", "enabled": true }, { "id": "rule_high_value_po", "name": "High-value orders require PO number", "expression": "amount <= 50000 || (poNumber && poNumber.trim().length > 0)", "errorMessage": "Orders over $50k require a PO number", "enabled": true }, { "id": "rule_unique_skus", "name": "No duplicate SKUs", "expression": "is_unique(lineItems.map(x => x.sku))", "errorMessage": "Line items must have unique SKU codes", "enabled": true }, { "id": "rule_email_format", "name": "Valid customer email", "expression": "is_email(customerEmail)", "errorMessage": "Customer email is not valid", "enabled": true } ] ``` --- ## EXPRESSION LANGUAGE REFERENCE Expressions are used in business rules and calculated fields. They are evaluated in a sandboxed environment with access to the payload fields as variables. ### Arithmetic Operators | Operator | Description | Example | |----------|-------------|---------| | `+` | Addition / string concatenation | `price + tax` | | `-` | Subtraction | `total - discount` | | `*` | Multiplication | `quantity * unitPrice` | | `/` | Division | `total / count` | | `%` | Modulo (remainder) | `value % 2` | | `^` | Exponentiation | `base ^ exponent` | ### Comparison Operators | Operator | Description | Example | |----------|-------------|---------| | `>` | Greater than | `amount > 100` | | `<` | Less than | `age < 18` | | `>=` | Greater than or equal | `score >= 80` | | `<=` | Less than or equal | `quantity <= 1000` | | `==` | Equality | `status == "active"` | | `!=` | Inequality | `currency != "USD"` | ### Logical Operators | Operator | Description | Example | |----------|-------------|---------| | `&&` | Logical AND | `age >= 18 && hasConsent` | | `\|\|` | Logical OR | `isAdmin \|\| isOwner` | | `!` | Logical NOT | `!isBlocked` | ### Ternary Operator ``` condition ? valueIfTrue : valueIfFalse ``` Example: `amount > 1000 ? "high" : "low"` ### Property Access ``` object.property — Dot notation object.nested.deep.value — Nested access array[0] — Array index access array.length — Array length ``` ### Math Functions All Math functions are called as `Math.functionName(args)`: | Function | Description | Example | |----------|-------------|---------| | `Math.abs(x)` | Absolute value | `Math.abs(balance)` | | `Math.ceil(x)` | Round up | `Math.ceil(price)` | | `Math.floor(x)` | Round down | `Math.floor(score)` | | `Math.round(x)` | Round to nearest | `Math.round(average)` | | `Math.sqrt(x)` | Square root | `Math.sqrt(variance)` | | `Math.pow(x, y)` | Power | `Math.pow(base, 2)` | | `Math.min(a, b, ...)` | Minimum | `Math.min(a, b, c)` | | `Math.max(a, b, ...)` | Maximum | `Math.max(x, y)` | | `Math.log(x)` | Natural logarithm | `Math.log(value)` | | `Math.log10(x)` | Base-10 logarithm | `Math.log10(value)` | | `Math.exp(x)` | e^x | `Math.exp(rate)` | | `Math.sin(x)` | Sine (radians) | `Math.sin(angle)` | | `Math.cos(x)` | Cosine (radians) | `Math.cos(angle)` | | `Math.tan(x)` | Tangent (radians) | `Math.tan(angle)` | | `Math.asin(x)` | Arc sine | `Math.asin(value)` | | `Math.acos(x)` | Arc cosine | `Math.acos(value)` | | `Math.atan(x)` | Arc tangent | `Math.atan(value)` | | `Math.atan2(y, x)` | Arc tangent of y/x | `Math.atan2(y, x)` | | `Math.random()` | Random 0-1 | `Math.random()` | ### String Methods Called on string values: | Method | Description | Example | |--------|-------------|---------| | `.toUpperCase()` | Convert to uppercase | `name.toUpperCase()` | | `.toLowerCase()` | Convert to lowercase | `code.toLowerCase()` | | `.trim()` | Remove whitespace | `input.trim()` | | `.length` | String length | `name.length` | | `.includes(str)` | Contains substring | `email.includes("@")` | | `.startsWith(str)` | Starts with | `id.startsWith("INV-")` | | `.endsWith(str)` | Ends with | `file.endsWith(".pdf")` | **Unsupported string methods** (will cause validation error): `substring`, `substr`, `slice`, `split`, `replace`, `replaceAll`, `match`, `matchAll`, `search`, `indexOf`, `lastIndexOf`, `charAt`, `charCodeAt`, `concat`, `repeat`, `padStart`, `padEnd`, `trimStart`, `trimEnd` ### Array Methods Called on array values. Support arrow function syntax: | Method | Description | Example | |--------|-------------|---------| | `.map(fn)` | Transform elements | `items.map(x => x.price)` | | `.filter(fn)` | Filter elements | `items.filter(x => x.quantity > 0)` | | `.find(fn)` | Find first match | `items.find(x => x.sku == "ABC")` | | `.some(fn)` | Any element matches | `items.some(x => x.price > 100)` | | `.every(fn)` | All elements match | `items.every(x => x.quantity > 0)` | | `.reduce(fn)` | Reduce to single value | `items.reduce((sum, x) => sum + x.price)` | **Unsupported array methods** (will cause validation error): `forEach`, `findIndex`, `join`, `slice`, `splice`, `push`, `pop`, `shift`, `unshift`, `sort`, `reverse`, `concat`, `indexOf`, `lastIndexOf`, `flat`, `flatMap`, `fill` ### Date Functions Top-level functions for date operations: | Function | Description | Example | |----------|-------------|---------| | `date_diff(d1, d2)` | Difference in days | `date_diff(endDate, startDate)` | | `date_add(date, days)` | Add days to date | `date_add(orderDate, 30)` | | `date_before(d1, d2)` | d1 is before d2 | `date_before(startDate, endDate)` | | `date_after(d1, d2)` | d1 is after d2 | `date_after(dueDate, now())` | | `date_equal(d1, d2)` | Dates are equal | `date_equal(shipDate, requestedDate)` | | `now()` | Current date/time | `date_before(expiryDate, now())` | ### Format Validation Functions Return `true` if the value matches the format: | Function | Description | Example | |----------|-------------|---------| | `is_email(val)` | Valid email | `is_email(contactEmail)` | | `is_url(val)` | Valid URL | `is_url(website)` | | `is_uuid(val)` | Valid UUID | `is_uuid(referenceId)` | | `is_phone(val)` | Valid phone number | `is_phone(mobile)` | | `is_iban(val)` | Valid IBAN | `is_iban(bankAccount)` | | `is_gstin(val)` | Valid GSTIN (India) | `is_gstin(taxId)` | | `is_pan(val)` | Valid PAN (India) | `is_pan(panNumber)` | ### Aggregate Functions For computing over arrays: | Function | Description | Example | |----------|-------------|---------| | `sum(array)` | Sum of values | `sum(scores)` | | `sum(array, "field")` | Sum of object field | `sum(lineItems, "amount")` | | `avg(array)` | Average | `avg(ratings)` | | `count(array)` | Count items | `count(items)` | | `min_val(array)` | Minimum value | `min_val(prices)` | | `min_val(array, "field")` | Min of field | `min_val(bids, "amount")` | | `max_val(array)` | Maximum value | `max_val(scores)` | | `max_val(array, "field")` | Max of field | `max_val(offers, "price")` | | `is_unique(array)` | All values unique | `is_unique(items.map(x => x.id))` | ### Text Analysis Functions For analyzing text content: | Function | Description | Example | |----------|-------------|---------| | `word_count(text)` | Count words | `word_count(content) >= 10` | | `char_count(text)` | Count characters | `char_count(summary) <= 500` | | `line_count(text)` | Count lines | `line_count(code) <= 100` | | `contains_word(text, word)` | Contains word | `contains_word(review, "excellent")` | ### String Pattern Matching | Function | Description | Example | |----------|-------------|---------| | `regex_match(str, pattern)` | Test regex pattern | `regex_match(zipCode, "^[0-9]{5}$")` | ### Forbidden Patterns (Security) These patterns are **blocked** and will cause expression validation to fail: - Code execution: `eval(`, `Function(`, `require(`, `import(` - Process access: `process.`, `global.`, `__proto__`, `constructor`, `prototype` - System access: `fs.`, `child_process`, `exec(`, `spawn(` - Constructors: `Array(`, `Array.` - Suspicious characters: `$`, `` ` ``, `;` - Keywords: `new`, `class`, `function`, `async`, `await`, `yield`, `import`, `export`, `delete`, `void`, `typeof`, `debugger`, `with`, `try`, `catch`, `throw`, `finally` --- ## FREETEXT MODE For gates that validate free-form text content instead of structured JSON. ### Configuration ```json { "validationMode": "freetext", "freetextConfig": { "contentFormat": "plaintext", "max_content_length": 100000, "codeLanguage": "javascript" } } ``` | Field | Type | Required | Values | |-------|------|----------|--------| | `contentFormat` | string | Yes | `"plaintext"`, `"markdown"`, `"html"`, `"code"` | | `max_content_length` | number | Yes | 1 to 500,000 characters | | `codeLanguage` | string | No | Language hint for `"code"` format (e.g., `"python"`, `"javascript"`) | ### Payload Format When submitting to a freetext gate, the payload must have a `content` field: ```json { "content": "Your text content here..." } ``` ### Freetext Validation 1. `content` must be a non-empty string 2. Length must not exceed `max_content_length` 3. Business rules can use text functions (`word_count`, `char_count`, `line_count`, `contains_word`) on the `content` variable ### Freetext Business Rule Examples ```json [ { "id": "rule_min_words", "name": "Minimum word count", "expression": "word_count(content) >= 50", "errorMessage": "Content must have at least 50 words", "enabled": true }, { "id": "rule_max_chars", "name": "Character limit", "expression": "char_count(content) <= 10000", "errorMessage": "Content must not exceed 10,000 characters", "enabled": true } ] ``` --- ## IDENTITY KEY FIELDS (Chain Correlation) Identity key fields enable automatic run chaining for self-correction workflows. When an agent submits a payload that fails validation, fixes the data, and resubmits, Flow automatically links the runs together. ### Configuration ```json { "runKeyFields": ["order_id"] } ``` Or with multiple fields: ```json { "runKeyFields": ["customer_id", "order_date"] } ``` ### How It Works 1. When `runKeyFields` are configured, Flow extracts the specified field values from the payload 2. A deterministic `correlationId` is computed: `SHA-256(gateId + sorted field=value pairs)` 3. All runs with the same field values share the same `correlationId` 4. When a run fails, the next run with the same key fields automatically gets `parentRunId` set to the failed run 5. This forms a self-correction chain visible in the run history ### Field Path Support Key fields support dot-notation for nested fields: ```json { "runKeyFields": ["order.id", "customer.account_number"] } ``` ### Edge Cases - If configured key fields are **missing from the payload**, a unique UUID `correlationId` is generated (no chaining occurs) - If `runKeyFields` is not configured, runs are independent unless `parentRunId` is explicitly provided - Key field values are sorted alphabetically before hashing for consistency --- ## GATE CREATION — REST API ### Create Gate ``` POST /api/flow/gates Authorization: Bearer Content-Type: application/json ``` ```json { "name": "Invoice Validation Gate", "description": "Validates vendor invoices before processing", "validationMode": "variables", "schema": { "type": "object", "properties": { "vendor_name": { "type": "string", "required": true, "minLength": 1 }, "invoice_number": { "type": "string", "required": true, "pattern": "^INV-[0-9]+$" }, "amount": { "type": "number", "required": true, "min": 0 }, "currency": { "type": "string", "allowedValues": ["USD", "EUR", "GBP"] }, "due_date": { "type": "date", "required": true } } }, "businessRules": [ { "id": "rule_amount_limit", "name": "Amount within limit", "expression": "amount <= 1000000", "errorMessage": "Invoice amount exceeds $1M limit", "enabled": true } ], "runKeyFields": ["invoice_number"], "approvalMode": "auto" } ``` ### Update Schema (Creates New Draft Version) ``` PATCH /api/flow/gates/:gateId/schema Authorization: Bearer Content-Type: application/json ``` ```json { "schema": { "type": "object", "properties": { ... } }, "businessRules": [ ... ], "runKeyFields": ["invoice_number"] } ``` ### Publish Gate ``` POST /api/flow/gates/:gateId/publish Authorization: Bearer Content-Type: application/json ``` ```json { "versionName": "v1.0 — initial release", "changeNotes": "Added invoice validation schema with amount limit rule" } ``` ### Rollback Version ``` POST /api/flow/gates/:gateId/rollback Authorization: Bearer Content-Type: application/json ``` ```json { "targetVersion": 2 } ``` --- ## RUN SUBMISSION ### Submit Run ``` POST /api/flow/gates/:gateIdOrSlug/run Authorization: Bearer Content-Type: application/json ``` ```json { "payload": { "vendor_name": "Acme Corp", "invoice_number": "INV-000123", "amount": 4500.00, "currency": "USD", "due_date": "2026-04-15" }, "metadata": { "source": "procurement-agent", "session_id": "abc123" } } ``` ### Run Response — Validation Passed ```json { "success": true, "runId": "550e8400-e29b-41d4-a716-446655440000", "shortId": "frun_a1b2c3d4", "status": "validated", "validation_id": "hmac-hash-for-tamper-detection", "validated_payload": { "vendor_name": "Acme Corp", "invoice_number": "INV-000123", "amount": 4500.00, "currency": "USD", "due_date": "2026-04-15" }, "correlationId": "sha256-hash-or-uuid", "parentRunId": null, "layers": { "schema": "pass", "business_rules": "pass" } } ``` ### Run Response — Validation Failed ```json { "success": false, "runId": "550e8400-e29b-41d4-a716-446655440001", "shortId": "frun_x9y8z7w6", "status": "validation_failed", "correlationId": "sha256-hash-or-uuid", "parentRunId": null, "layers": { "schema": "fail", "business_rules": "skipped" }, "error": { "code": "ERR_FLOW_RUN_002", "message": "Validation failed", "layer": "schema", "details": [ { "field": "vendor_name", "message": "is required", "code": "required" }, { "field": "amount", "message": "must be >= 0", "code": "minimum" } ] } } ``` ### Run Response — Business Rule Failed ```json { "success": false, "runId": "...", "status": "validation_failed", "layers": { "schema": "pass", "business_rules": "fail" }, "error": { "code": "ERR_FLOW_RUN_002", "message": "Validation failed", "layer": "business_rules", "details": [ { "field": null, "message": "Invoice amount exceeds $1M limit", "code": "business_rule", "rule_id": "rule_amount_limit", "context": { "expression": "amount <= 1000000", "computed_value": false } } ] } } ``` ### Validation Error Codes | Code | Meaning | Example | |------|---------|---------| | `required` | Required field missing | `vendor_name is required` | | `type` | Wrong type | `Expected number, got string` | | `format` | Format mismatch | `Invalid email format` | | `minimum` | Below min | `must be >= 0` | | `maximum` | Above max | `must be <= 1000000` | | `exclusiveMinimum` | At or below exclusive min | `must be > 0` | | `exclusiveMaximum` | At or above exclusive max | `must be < 100` | | `minLength` | String too short | `must be at least 3 characters` | | `maxLength` | String too long | `must be at most 255 characters` | | `pattern` | Regex mismatch | `does not match pattern ^INV-[0-9]+$` | | `multipleOf` | Not a multiple | `must be a multiple of 0.01` | | `allowedValues` | Not in whitelist | `must be one of: USD, EUR, GBP` | | `business_rule` | Rule evaluated to false | Rule's `errorMessage` is used | | `business_rule_error` | Rule threw an error | Runtime evaluation error | ### Run Statuses | Status | Description | |--------|-------------| | `validating` | Schema + rules being evaluated | | `validated` | All validation passed | | `validation_failed` | Schema or rules failed | | `rendering` | Document being generated (if renderMode configured) | | `rendered` | Document generated | | `render_failed` | Document generation failed | | `pending_approval` | Waiting for human review | | `approved` | Reviewer approved | | `rejected` | Reviewer rejected | | `delivering` | Webhook being sent | | `delivered` | Webhook delivered | | `delivery_failed` | Webhook delivery failed | | `completed` | All pipeline stages done | --- ## APPROVAL CONFIGURATION ### Auto-Approve (Default) ```json { "approvalMode": "auto" } ``` Runs that pass validation are automatically approved and proceed to delivery. ### Manual Approval ```json { "approvalMode": "manual", "approvalConfig": { "approvers": [ { "type": "internal", "email": "reviewer@company.com", "userId": "uuid" }, { "type": "external", "email": "vendor@supplier.com" } ], "timeout_hours": 48, "condition": "amount > 10000" } } ``` | Field | Description | |-------|-------------| | `approvers` | List of reviewers (internal = platform user, external = magic link) | | `timeout_hours` | Hours before approval request expires (1-720) | | `condition` | Optional expression — only require approval when true | External reviewers receive a magic link via email — no account required. --- ## DELIVERY CONFIGURATION ### Webhook Delivery ```json { "deliveryChannels": [ { "type": "webhook", "config": { "url": "https://api.example.com/webhooks/flow", "headers": { "Authorization": "Bearer your-token" } } } ] } ``` Webhooks are sent after a run reaches approved/completed state. Includes HMAC signature for verification. **Webhook headers sent:** - `X-Rynko-Signature` — HMAC-SHA256 signature - `X-Rynko-Timestamp` — Unix timestamp - `X-Rynko-Event-Id` — Unique event ID - `X-Rynko-Event-Type` — Event type --- ## PLATFORM LIMITS | Limit | Value | |-------|-------| | Max request body size | 1 MB | | Max business rules per gate | 20 | | Max expression length | 500 characters | | Max gate name length | 255 characters | | Max gate description length | 1,000 characters | | Max object nesting depth | 5 levels | | Max freetext content | 500,000 characters (configurable per gate) | | Approval timeout range | 1 – 720 hours | | Rate limit range | 1 – 1,000 per minute (per gate) | | Digest window range | 1 – 60 minutes | | Cooldown minimum | 60 seconds | | List pagination max | 100 items per page | --- ## CONVERTING FROM PYDANTIC / ZOD SCHEMAS When an LLM has a Pydantic model or Zod schema from agent code, convert it to Flow's ObjectSchema format. ### Pydantic → Flow ObjectSchema **Pydantic model:** ```python from pydantic import BaseModel, Field from typing import Optional, List from datetime import date class LineItem(BaseModel): sku: str description: str = "" quantity: int = Field(ge=1) unit_price: float = Field(ge=0.01) class Invoice(BaseModel): invoice_number: str = Field(pattern=r"^INV-\d{6}$") vendor_name: str = Field(min_length=1, max_length=255) amount: float = Field(ge=0, le=1000000) currency: str = "USD" due_date: date line_items: List[LineItem] notes: Optional[str] = None ``` **Conversion rules:** | Pydantic | Flow | |----------|------| | `str` | `"string"` | | `int`, `float` | `"number"` | | `bool` | `"boolean"` | | `date`, `datetime` | `"date"` | | `List[T]` | `"array"` with `itemType` | | `BaseModel` (nested) | `"object"` with `properties` | | No default / no `Optional` | `"required": true` | | `Optional[T]` / has default | `"required": false` | | `Field(min_length=N)` | `"minLength": N` | | `Field(max_length=N)` | `"maxLength": N` | | `Field(ge=N)` | `"min": N` | | `Field(le=N)` | `"max": N` | | `Field(gt=N)` | `"exclusiveMin": N` | | `Field(lt=N)` | `"exclusiveMax": N` | | `Field(multiple_of=N)` | `"multipleOf": N` | | `Field(pattern=...)` | `"pattern": "..."` | | `Literal["a","b","c"]` | `"allowedValues": ["a","b","c"]` | | Default value | `"defaultValue": value` | **Resulting Flow schema:** ```json { "type": "object", "properties": { "invoice_number": { "type": "string", "required": true, "pattern": "^INV-\\d{6}$" }, "vendor_name": { "type": "string", "required": true, "minLength": 1, "maxLength": 255 }, "amount": { "type": "number", "required": true, "min": 0, "max": 1000000 }, "currency": { "type": "string", "required": false, "defaultValue": "USD" }, "due_date": { "type": "date", "required": true }, "line_items": { "type": "array", "required": true, "itemType": "object", "schema": { "type": "object", "properties": { "sku": { "type": "string", "required": true }, "description": { "type": "string", "required": false, "defaultValue": "" }, "quantity": { "type": "number", "required": true, "min": 1 }, "unit_price": { "type": "number", "required": true, "min": 0.01 } } } }, "notes": { "type": "string", "required": false } } } ``` ### Zod → Flow ObjectSchema **Zod schema:** ```typescript import { z } from 'zod'; const LineItemSchema = z.object({ sku: z.string(), description: z.string().default(""), quantity: z.number().int().min(1), unitPrice: z.number().min(0.01), }); const InvoiceSchema = z.object({ invoiceNumber: z.string().regex(/^INV-\d{6}$/), vendorName: z.string().min(1).max(255), amount: z.number().min(0).max(1000000), currency: z.enum(["USD", "EUR", "GBP"]).default("USD"), dueDate: z.coerce.date(), lineItems: z.array(LineItemSchema), notes: z.string().optional(), }); ``` **Conversion rules:** | Zod | Flow | |-----|------| | `z.string()` | `"string"` | | `z.number()` | `"number"` | | `z.boolean()` | `"boolean"` | | `z.date()` / `z.coerce.date()` | `"date"` | | `z.array(T)` | `"array"` with `itemType` | | `z.object({})` (nested) | `"object"` with `properties` | | No `.optional()` / no `.default()` | `"required": true` | | `.optional()` / `.nullable()` | `"required": false` | | `.default(val)` | `"required": false, "defaultValue": val` | | `.min(N)` on string | `"minLength": N` | | `.max(N)` on string | `"maxLength": N` | | `.min(N)` on number | `"min": N` | | `.max(N)` on number | `"max": N` | | `.gt(N)` | `"exclusiveMin": N` | | `.lt(N)` | `"exclusiveMax": N` | | `.multipleOf(N)` | `"multipleOf": N` | | `.regex(/pattern/)` | `"pattern": "pattern"` | | `.email()` | `"format": "email"` | | `.url()` | `"format": "url"` | | `.uuid()` | `"format": "uuid"` | | `z.enum(["a","b"])` | `"allowedValues": ["a","b"]` | **Resulting Flow schema:** ```json { "type": "object", "properties": { "invoiceNumber": { "type": "string", "required": true, "pattern": "^INV-\\d{6}$" }, "vendorName": { "type": "string", "required": true, "minLength": 1, "maxLength": 255 }, "amount": { "type": "number", "required": true, "min": 0, "max": 1000000 }, "currency": { "type": "string", "required": false, "defaultValue": "USD", "allowedValues": ["USD", "EUR", "GBP"] }, "dueDate": { "type": "date", "required": true }, "lineItems": { "type": "array", "required": true, "itemType": "object", "schema": { "type": "object", "properties": { "sku": { "type": "string", "required": true }, "description": { "type": "string", "required": false, "defaultValue": "" }, "quantity": { "type": "number", "required": true, "min": 1 }, "unitPrice": { "type": "number", "required": true, "min": 0.01 } } } }, "notes": { "type": "string", "required": false } } } ``` ### JSON Schema → Flow ObjectSchema For standard JSON Schema (e.g., from OpenAPI specs): | JSON Schema | Flow | |-------------|------| | `"type": "string"` | `"type": "string"` | | `"type": "integer"` / `"number"` | `"type": "number"` | | `"type": "boolean"` | `"type": "boolean"` | | `"format": "date"` / `"date-time"` | `"type": "date"` | | `"type": "array", "items": {...}` | `"type": "array", "itemType": ..., "schema": {...}` | | `"type": "object", "properties": {...}` | `"type": "object", "properties": {...}` | | `"required": ["field1"]` (parent-level) | `"required": true` (per-field) | | `"minLength"` | `"minLength"` | | `"maxLength"` | `"maxLength"` | | `"minimum"` | `"min"` | | `"maximum"` | `"max"` | | `"exclusiveMinimum"` | `"exclusiveMin"` | | `"exclusiveMaximum"` | `"exclusiveMax"` | | `"multipleOf"` | `"multipleOf"` | | `"pattern"` | `"pattern"` | | `"enum"` | `"allowedValues"` | | `"default"` | `"defaultValue"` | | `"description"` | `"description"` | **Key difference**: JSON Schema puts `required` as an array on the parent object. Flow puts `required: true` on each individual property. --- ## COMPLETE EXAMPLE SCHEMAS ### Example 1: E-Commerce Order Validation ```json { "name": "Order Validation Gate", "description": "Validates e-commerce orders from the checkout agent", "validationMode": "variables", "schema": { "type": "object", "properties": { "order_id": { "type": "string", "required": true, "pattern": "^ORD-[A-Z0-9]{8}$", "description": "Unique order identifier" }, "customer": { "type": "object", "required": true, "properties": { "name": { "type": "string", "required": true, "minLength": 1 }, "email": { "type": "string", "required": true, "format": "email" }, "phone": { "type": "string", "format": "phone" } } }, "items": { "type": "array", "required": true, "itemType": "object", "schema": { "type": "object", "properties": { "product_id": { "type": "string", "required": true }, "name": { "type": "string", "required": true }, "quantity": { "type": "number", "required": true, "min": 1, "max": 100 }, "unit_price": { "type": "number", "required": true, "min": 0.01 } } } }, "subtotal": { "type": "number", "required": true, "min": 0 }, "tax": { "type": "number", "required": true, "min": 0 }, "total": { "type": "number", "required": true, "min": 0 }, "currency": { "type": "string", "required": true, "allowedValues": ["USD", "EUR", "GBP", "INR"] }, "shipping_address": { "type": "object", "required": true, "properties": { "street": { "type": "string", "required": true }, "city": { "type": "string", "required": true }, "state": { "type": "string" }, "postal_code": { "type": "string", "required": true }, "country": { "type": "string", "required": true, "minLength": 2, "maxLength": 2 } } } } }, "businessRules": [ { "id": "rule_total_match", "name": "Total matches subtotal + tax", "expression": "Math.abs(total - (subtotal + tax)) < 0.01", "errorMessage": "Total does not match subtotal + tax", "enabled": true }, { "id": "rule_subtotal_match", "name": "Subtotal matches line items", "expression": "Math.abs(subtotal - sum(items.map(x => x.quantity * x.unit_price))) < 0.01", "errorMessage": "Subtotal does not match sum of line items", "enabled": true }, { "id": "rule_has_items", "name": "Order must have items", "expression": "count(items) > 0", "errorMessage": "Order must contain at least one item", "enabled": true }, { "id": "rule_unique_products", "name": "No duplicate products", "expression": "is_unique(items.map(x => x.product_id))", "errorMessage": "Each product should appear only once (combine quantities instead)", "enabled": true } ], "runKeyFields": ["order_id"], "approvalMode": "manual", "approvalConfig": { "approvers": [ { "type": "internal", "email": "orders@company.com" } ], "timeout_hours": 24, "condition": "total > 5000" } } ``` ### Example 2: Content Review Gate (Freetext) ```json { "name": "Blog Content Review", "description": "Reviews AI-generated blog posts before publishing", "validationMode": "freetext", "freetextConfig": { "contentFormat": "markdown", "max_content_length": 50000 }, "businessRules": [ { "id": "rule_min_length", "name": "Minimum content length", "expression": "word_count(content) >= 300", "errorMessage": "Blog post must be at least 300 words", "enabled": true }, { "id": "rule_max_length", "name": "Maximum content length", "expression": "word_count(content) <= 5000", "errorMessage": "Blog post must not exceed 5,000 words", "enabled": true } ], "approvalMode": "manual", "approvalConfig": { "approvers": [ { "type": "internal", "email": "editor@company.com" } ], "timeout_hours": 72 } } ``` ### Example 3: Financial Transaction Validation ```json { "name": "Transaction Validation", "description": "Validates financial transactions from the payment processing agent", "validationMode": "variables", "schema": { "type": "object", "properties": { "transaction_id": { "type": "string", "required": true, "format": "uuid" }, "type": { "type": "string", "required": true, "allowedValues": ["credit", "debit", "transfer"] }, "amount": { "type": "number", "required": true, "exclusiveMin": 0, "max": 10000000 }, "currency": { "type": "string", "required": true, "minLength": 3, "maxLength": 3 }, "from_account": { "type": "string", "required": true }, "to_account": { "type": "string" }, "reference": { "type": "string", "maxLength": 500 }, "timestamp": { "type": "date", "required": true }, "metadata": { "type": "object", "properties": { "ip_address": { "type": "string" }, "user_agent": { "type": "string" }, "geo_country": { "type": "string" } } } } }, "businessRules": [ { "id": "rule_transfer_needs_to", "name": "Transfers require destination", "expression": "type != 'transfer' || (to_account && to_account.trim().length > 0)", "errorMessage": "Transfer transactions require a destination account", "enabled": true }, { "id": "rule_no_self_transfer", "name": "No self-transfers", "expression": "type != 'transfer' || from_account != to_account", "errorMessage": "Cannot transfer to the same account", "enabled": true }, { "id": "rule_future_date", "name": "No future transactions", "expression": "!date_after(timestamp, now())", "errorMessage": "Transaction timestamp cannot be in the future", "enabled": true } ], "runKeyFields": ["transaction_id"] } ``` --- ## ERROR HANDLING ### API Error Codes | Code | HTTP | Description | |------|------|-------------| | `ERR_FLOW_GATE_001` | 404 | Gate not found | | `ERR_FLOW_GATE_002` | 400 | Invalid schema | | `ERR_FLOW_GATE_003` | 400 | No draft to publish | | `ERR_FLOW_RUN_001` | 404 | Gate not found (on submission) | | `ERR_FLOW_RUN_002` | 400 | Validation failed | | `ERR_FLOW_RUN_003` | 429 | Circuit breaker open | | `ERR_FLOW_RUN_004` | 429 | Rate limit exceeded | | `ERR_FLOW_RUN_005` | 413 | Payload exceeds 1MB | ### Rate Limit Response ```json { "success": false, "error": { "code": "ERR_FLOW_RUN_004", "message": "Submission rate limit exceeded" }, "retryAfter": 30 } ``` --- ## TIPS FOR LLMs CREATING GATE SCHEMAS 1. **Start with the payload shape.** Identify all fields the agent will submit. Map each to the correct type. 2. **Mark required fields explicitly.** Every field that must be present should have `"required": true`. Fields with defaults or optional data should be `"required": false`. 3. **Use constraints generously.** Add `min`/`max` for numbers, `minLength`/`maxLength` for strings, `format` for emails/URLs/UUIDs, `pattern` for custom formats, `allowedValues` for enums. 4. **Business rules for cross-field logic.** Schema constraints validate individual fields. Business rules validate relationships between fields (e.g., `total == subtotal + tax`). 5. **Use identity key fields.** If the agent might retry failed submissions (self-correction), set `runKeyFields` to the natural identifier fields (e.g., `["order_id"]`). 6. **Keep expressions simple.** Each expression should test one condition. Combine with `&&` / `||` only when the conditions are tightly related. 7. **Use aggregate functions for arrays.** `sum(items, "amount")` is cleaner than `items.reduce(...)` for simple aggregations. 8. **Set meaningful error messages.** The `errorMessage` is what the agent sees when a rule fails — make it actionable (e.g., "Amount must be positive" not "Rule failed"). 9. **Use freetext mode for unstructured content.** If the gate validates free-form text (articles, code, reviews), use `validationMode: "freetext"` instead of a single `content` string field. 10. **Test with intentionally bad data.** Submit payloads that violate each constraint to verify error messages are clear and actionable.