Gates
A gate is a named validation checkpoint. It defines what data structure you expect, what business rules must hold, and what happens after validation succeeds (rendering, approval, delivery).
Gate Identifiers​
Every gate has three identifiers you can use interchangeably in API calls:
| Type | Format | Example |
|---|---|---|
| UUID | 36 characters | 550e8400-e29b-41d4-a716-446655440000 |
| shortId | fgate_ + 8 chars | fgate_a1b2c3d4 |
| slug | lowercase + hyphens | order-validation |
The slug is auto-generated from the gate name and is unique within your workspace.
Naming Best Practices​
Gate names and descriptions are surfaced to AI agents as MCP tool metadata. Writing them well helps agents pick the correct gate without confusion.
Name​
Keep the name short and scannable — it becomes part of the MCP tool name (submit_{slug}). Good names identify the domain object and action:
| Good | Avoid |
|---|---|
Order Validation | Gate for validating incoming e-commerce orders from the checkout flow |
Invoice Review | InvoiceGate |
Content Moderation | AI Output Check v2 |
Description​
Write 1–2 sentences explaining when to use this gate, not what it checks. Agents read the description to decide whether a gate applies; the schema and business rules already document the validation details.
Use the "Submit if…" pattern — it tells agents exactly when to route data to this gate.
Good examples:
Submit if this is a transit-update email envelope with attachment metadata.
Submit if you assembled a complete invoice payload (hub, lines, totals, currency) ready for document generation.
Validates customer orders before they enter the fulfillment pipeline. Use for all checkout-originated orders.
Too verbose — don't repeat schema/rule details:
This gate validates orders by checking that the customer name is a non-empty string, the email matches a valid format, the amount is a positive number greater than zero, the order date is present, and the shipping address contains street, city, and zip fields. It also enforces that quantity * unitPrice equals totalAmount.
Multi-gate pipelines​
When you have multiple gates forming a processing pipeline, the "Submit if…" pattern is especially useful — each description scopes exactly which stage of the pipeline the gate handles:
Gate 1: "Submit if you parsed a transit Excel sheet into rows and
need a structured batch for DATA-1 processing."
Gate 2: "Submit if you normalized DATA-1 sources into shipment
objects and want only clean, delta-aware shipments for merging."
Gate 3: "Submit if you produced the authoritative merged shipment
set for invoicing and reconciliation."
Each gate describes its own input boundary without referencing downstream steps. This lets agents match the right gate based on what they currently have, not what happens next.
Slug versioning​
Adding a version suffix to your slug (e.g., invoice-vmi-payload-v1) lets you introduce breaking schema changes under a new slug while keeping the old gate active for in-flight runs.
Where validation details belong​
| Detail | Where to put it |
|---|---|
| Field types and constraints | Schema properties |
| Field-level documentation | Schema property description field |
| Cross-field rules | Business Rules with descriptive name and errorMessage |
| When to use this gate | Gate description |
Schema Definition​
Gate schemas use an object format to define expected fields:
{
"type": "object",
"properties": {
"customerName": { "type": "string", "required": true },
"email": { "type": "string", "required": true, "format": "email" },
"amount": { "type": "number", "required": true, "min": 0 },
"isPriority": { "type": "boolean", "required": false },
"orderDate": { "type": "date", "required": true },
"tags": { "type": "array", "itemType": "string" },
"address": {
"type": "object",
"properties": {
"street": { "type": "string", "required": true },
"city": { "type": "string", "required": true },
"zip": { "type": "string", "required": true }
}
}
}
}
Supported Types​
| Type | Description | Constraints |
|---|---|---|
string | Text value | minLength, maxLength, format, allowedValues |
number | Numeric value | min, max |
boolean | True/false | — |
date | Date string | — |
array | List of items | itemType (required) |
object | Nested object | properties (required) |
String Formats​
The format constraint validates string patterns:
| Format | Validates |
|---|---|
email | Email address |
url | URL |
Allowed Values​
Restrict a field to a set of valid options:
{
"status": {
"type": "string",
"required": true,
"allowedValues": ["pending", "active", "completed", "cancelled"]
}
}
Import from Template​
If you have an existing Rynko document template, you can import its variables as a gate schema:
curl -X POST https://api.rynko.dev/api/flow/gates/ORDER_GATE_ID/import-schema \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "templateId": "invoice-template" }'
This copies the template's variable definitions into the gate schema and sets the gate's validation mode to variables.
Import from Pydantic or Zod​
If your validation logic already lives in a Pydantic model (Python) or a Zod schema (TypeScript), you can paste its JSON Schema output directly into the gate — no need to rebuild the schema from scratch.
Generate the JSON Schema output​
- Pydantic (Python)
- Zod (TypeScript)
import json
from pydantic import BaseModel
from typing import Optional
from datetime import date
class OrderModel(BaseModel):
customer_name: str
email: str
amount: float
quantity: int
order_date: date
notes: Optional[str] = None
# Pydantic v2
print(json.dumps(OrderModel.model_json_schema(), indent=2))
# Pydantic v1
# print(json.dumps(OrderModel.schema(), indent=2))
Install zod-to-json-schema first:
npm install zod-to-json-schema
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
const OrderSchema = z.object({
customerName: z.string(),
email: z.string().email(),
amount: z.number().positive(),
quantity: z.number().int().min(1),
orderDate: z.string().date(),
notes: z.string().optional(),
});
console.log(JSON.stringify(zodToJsonSchema(OrderSchema), null, 2));
Copy the printed JSON output to your clipboard.
Import in the dashboard​
- Open your gate and click Schema & Validate.
- Click Import from Pydantic / Zod.
- Select the Pydantic or Zod tab.
- Paste the JSON output into the text area.
- Click Parse Schema to preview what will be imported.
- Review any warnings, then click Import.
The import replaces the existing schema and merges any generated business rules into your existing ones (existing rules are preserved).
What gets imported​
| Schema feature | Imported as |
|---|---|
| Object properties | Gate schema properties with mapped types |
required fields | required: true on each property |
minimum / maximum | min / max constraints |
minLength / maxLength | String length constraints |
pattern | Regex pattern constraint |
enum / const | allowedValues constraint |
default | Default value constraint |
format: date or date-time | date type |
format: email | String with email format |
integer type | number type + multipleOf: 1 constraint |
Nested $ref objects | Nested object properties (resolved recursively) |
anyOf / oneOf with a null variant | Nullable field (required: false) |
if / then / else | Business rule (where convertible) |
uniqueItems: true on an array | Business rule checking array uniqueness |
not constraint | Business rule (where convertible) |
Understanding import warnings​
Any part of the schema that could not be converted automatically produces a warning. Warnings are shown immediately after parsing — review each one and resolve it in the Schema Editor or Business Rules section before saving.
| Warning type | What happened | What to do |
|---|---|---|
| union type | The field is a union of multiple incompatible types (e.g. Union[A, B, C]). Only the first type was imported. | Open the Schema Editor and correct the field type, or split it into separate fields. |
| allOf merge conflict | Two allOf schemas defined conflicting values for the same key. The last value was used. | Verify the field's settings in the Schema Editor. |
| depth limit | The model exceeds 15 levels of nesting. Properties beyond that were not imported. | Add the missing nested fields manually in the Schema Editor. |
| not | A not constraint is too complex to auto-convert. It was skipped. | Add a Business Rule manually using !(expression) syntax. |
| if/then/else skipped | A conditional rule's condition uses unsupported patterns. The rule was skipped entirely. | Add a Business Rule manually to replicate the logic. |
| if/then/else truncated | A generated conditional rule exceeded the 450-character expression limit and was truncated. | Open Business Rules, find the affected rule, and shorten or rewrite it. |
After importing, click Edit Schema to review all properties, then expand Business Rules to check any auto-generated rules. Don't activate the gate until all warnings are resolved.
Limits​
$refreferences are resolved recursively; circular references are safely treated as plainobject.- Maximum nesting depth: 15 levels.
- Maximum business rules per gate: 20. If the import would push you over this limit, remove some existing rules first or reduce the rules in the imported schema.
Business Rules​
Business rules are cross-field expressions that enforce domain logic beyond simple type checking.
Rule Structure​
{
"businessRules": [
{
"id": "rule_date_order",
"name": "End date after start date",
"expression": "endDate > startDate",
"errorMessage": "End date must be after start date",
"enabled": true
},
{
"id": "rule_total_check",
"name": "Line items match total",
"expression": "quantity * unitPrice == totalAmount",
"errorMessage": "Total amount does not match quantity * unit price",
"enabled": true
}
]
}
Expression Syntax​
Expressions have access to all payload fields as variables. Supported operations:
| Category | Examples |
|---|---|
| Comparison | >, <, >=, <=, ==, != |
| Logical | &&, ||, ! |
| Arithmetic | +, -, *, /, % |
| Math functions | round(), max(), min(), abs(), pow() |
| String access | Field values are available directly by name |
A rule passes when its expression evaluates to a truthy value. All enabled rules are evaluated independently (no short-circuit) — you'll see all failures at once.
Custom Error Messages with fail()​
Use the fail() function to return context-specific error messages that include actual payload values:
{
"id": "rule_currency",
"name": "Supported currency",
"expression": "currency == \"USD\" || currency == \"EUR\" ? true : fail(\"Unsupported currency: \" + currency)",
"errorMessage": "Unsupported currency (fallback)",
"enabled": true
}
When fail() is called, its message replaces the static errorMessage. This is useful for:
- Including the actual invalid value:
fail("Expected positive amount, got " + amount) - Suggesting fixes:
fail("Unknown SKU: " + sku + ". Check the product catalog.")
Limits​
- Maximum 20 business rules per gate
- Expression maximum length: 500 characters
- Expressions are security-validated at save time (no
eval,require, or prototype access)
Validation Modes​
Variables Mode (default)​
Validates a JSON object against the schema. This is the standard mode for structured payloads.
Freetext Mode​
Validates unstructured text content (articles, reports, code snippets). Configure with:
{
"validationMode": "freetext",
"freetextConfig": {
"contentFormat": "markdown",
"max_content_length": 50000
}
}
| Content Format | Description |
|---|---|
plaintext | Plain text |
markdown | Markdown formatted |
html | HTML content |
code | Source code (set codeLanguage for syntax context) |
In freetext mode, business rules are not evaluated.
Gate Configuration​
Approval​
Enable human review before delivery:
{
"approvalMode": "manual",
"approvalConfig": {
"approvers": [
{ "type": "internal", "email": "reviewer@company.com" },
{ "type": "external", "email": "client@partner.com" }
],
"timeout_hours": 48
}
}
auto(default) — runs proceed directly after validationmanual— runs wait for an approver to approve or reject
See Approvals for details on the review workflow.
Delivery​
Forward validated (and approved) payloads to external systems:
{
"deliveryChannels": [
{
"type": "webhook",
"config": {
"url": "https://api.yourapp.com/webhook",
"headers": { "X-Custom-Header": "value" }
}
}
]
}
Webhook deliveries include an HMAC-SHA256 signature in the X-Rynko-Signature header for verification. Failed deliveries are retried up to 3 times with exponential backoff.
Rendering​
Optionally render a document from the validated payload:
{
"renderMode": "rynko",
"renderTemplateId": "invoice-template",
"renderConfig": { "format": "pdf" }
}
| Render Mode | Description |
|---|---|
none (default) | No rendering — validation only |
rynko | Render a Rynko document template using the payload as variables |
custom_api | Forward to a custom rendering API |
Rate Limiting​
Throttle submissions per gate:
{
"maxSubmissionsPerMinute": 60
}
When the limit is exceeded, the API returns HTTP 429 with a retryAfter value in seconds.
Circuit Breaker​
Automatically pause a gate when failures spike:
{
"circuitBreakerEnabled": true,
"maxFailures": 5,
"cooldownSeconds": 300
}
After maxFailures consecutive failures, the circuit opens and rejects new submissions for cooldownSeconds. This prevents cascading failures.
Notifications​
Control how you're notified about gate activity:
{
"notificationMode": "digest",
"notificationDigestMinutes": 5
}
| Mode | Behavior |
|---|---|
immediate | Notify on every event |
digest (default) | Batch notifications every N minutes |
Gate Status​
| Status | Description |
|---|---|
active | Accepting submissions (default) |
paused | Rejecting new submissions |
archived | Soft-deleted, not visible in listings |
Update a gate's status:
curl -X PUT https://api.rynko.dev/api/flow/gates/GATE_ID \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "status": "paused" }'
Schema Versioning​
Every schema update creates a new version. Each run records the schema version it was validated against. List versions:
curl https://api.rynko.dev/api/flow/gates/GATE_ID/versions \
-H "Authorization: Bearer YOUR_API_KEY"
This lets you track how your validation requirements have evolved and correlate runs with the schema version they used.