REST DSL¶
The rest: block is a higher-level convenience over workflows: for
HTTP-shaped APIs. One rest: entry declares a base path, a set of
HTTP verbs, optional CORS / security, and the target route URI for
each verb. The reifier expands it into:
- One
http-server://route per verb (each restricted to its own HTTP method viahttpMethodRestrict=<METHOD>). - One
GET /openapi.jsonroute that serves a generated OpenAPI 3.0.3 spec for the entirerest:block. - Optional CORS pre-flight headers set as
SetHeadersteps before the forward to the verb's target. - Path-parameter extraction (e.g.
/users/{id}→idis set on the exchange as a header).
Source: pkg/dsl/rest.go (fluent Go builder), pkg/dsl/rest_reifier.go
(YAML → runtime routes), pkg/dsl/rest_openapi.go (OpenAPI generator).
Block shape¶
rest:
- path: "/api/v1"
port: 8080
host: "0.0.0.0"
tag: "Users"
description: "User management API"
consumes: "application/json"
produces: "application/json"
binding-mode: "auto"
cors:
allowed-origins: ["http://localhost:3000"]
allowed-methods: ["GET", "POST", "PUT", "DELETE"]
allowed-headers: ["Content-Type", "Authorization"]
max-age: 3600
security:
- type: bearer
scheme: "JWT"
verbs:
- method: get
path: "/users"
target: direct://listUsers
description: "List all users"
- method: get
path: "/users/{id}"
target: direct://getUser
params:
- name: id
type: path
data-type: integer
required: true
description: "User ID"
- method: post
path: "/users"
target: direct://createUser
consumes: "application/json"
request-type: "User"
response-type: "User"
Top-level fields¶
Decoded into workflow.RestDefinition (pkg/workflow/rest_definition.go).
| Field | Type | Default | Description |
|---|---|---|---|
path |
string | required | Base path prefix shared by every verb. |
port |
int | 8080 |
HTTP listen port. |
host |
string | 0.0.0.0 |
HTTP listen interface. |
tag |
string | empty | Used as the OpenAPI tag for grouping. |
description |
string | empty | Shown in the OpenAPI tag description. |
consumes |
MIME | empty | Default Content-Type for verbs that have a body. |
produces |
MIME | empty | Default response Content-Type. Set as a SetHeader step before every verb's target. |
binding-mode |
enum | off |
off, auto, json, xml. Accepted by the parser, surfaced in the OpenAPI spec — actual content negotiation is upstream. |
cors |
object | unset | See CORS. |
security |
list | empty | See Security. |
verbs |
list | required | One or more verb definitions. |
port + host only apply when both are unset; otherwise the runtime
fallback host (e.g. --bind passed to helix run) is used.
Verbs¶
Each entry maps to workflow.VerbDefinition.
| Field | Type | Default | Description |
|---|---|---|---|
method |
enum | required | get / post / put / delete / patch / head / options. Case-insensitive; normalised to upper-case before reifying. |
path |
string | empty | Path suffix joined to the block-level path. Use {name} for path parameters. |
target |
URI | required | Destination route URI — usually direct://<id> pointing at a workflow that does the real work. |
consumes |
MIME | inherits | Verb-level override of the block's consumes. |
produces |
MIME | inherits | Verb-level override of the block's produces. |
description |
string | empty | Becomes the OpenAPI summary. |
request-type |
string | empty | Schema name referenced in the OpenAPI requestBody (#/components/schemas/<RequestType>). |
response-type |
string | empty | Schema name for the 200 response. |
params |
list | empty | Path / query / header / body params — see Parameters. |
Path / query / header / body parameters¶
verbs:
- method: get
path: "/users/{id}/orders"
target: direct://getOrders
params:
- name: id
type: path
data-type: integer
required: true
description: "User ID"
- name: status
type: query
data-type: string
required: false
description: "Filter by status"
default: "active"
- name: X-Trace-Id
type: header
data-type: string
required: false
| Field | Type | Default | Description |
|---|---|---|---|
name |
string | required | Parameter name. Path params must match a {name} placeholder in the path. |
type |
enum | required | path, query, header, body. |
data-type |
enum | string |
string, integer, boolean, number. Surfaces in the OpenAPI schema; the engine doesn't currently coerce, the upstream handler reads strings. |
required |
bool | false |
Path params must be true. Reflected in the OpenAPI spec. |
description |
string | empty | OpenAPI description. |
default |
string | empty | Default value for query params. Reflected in OpenAPI but not enforced — pass the param explicitly if you need it. |
Path params declared in the params: list are matched against
{id}-style placeholders in path. Placeholders that don't have a
params: entry are still extracted automatically and added to the
OpenAPI spec as required string parameters.
When the request arrives, the path-extraction processor sets each
captured value on the exchange's In() headers under the param name.
For /users/{id}/orders/{orderId} matched against
/users/42/orders/99, the downstream route sees
headers["id"] = "42" and headers["orderId"] = "99".
CORS¶
| Field | Type | Default | Description |
|---|---|---|---|
allowed-origins |
[]string | empty | Joined with , into Access-Control-Allow-Origin. |
allowed-methods |
[]string | empty | Joined into Access-Control-Allow-Methods. |
allowed-headers |
[]string | empty | Joined into Access-Control-Allow-Headers. |
max-age |
int | 0 |
Seconds — only emitted when > 0. |
CORS headers are pre-pended as set-header: steps before each verb's
target, so every response carries them. There's no explicit OPTIONS
pre-flight handler — declare an options: verb if you need one.
Security¶
| Field | Type | Description |
|---|---|---|
type |
enum | basic / bearer / apiKey / oauth2. |
name |
string | apiKey param/header name. Defaults to X-API-Key. |
in |
enum | header / query. Defaults to header. |
scheme |
string | For bearer, the OpenAPI scheme name (defaults to bearer). |
security: only feeds the OpenAPI spec — it documents the auth
requirements that an upstream gateway or per-route auth: block must
actually enforce. To require auth at runtime, use auth: on the
target workflow.
How a rest: block reifies¶
For each verb the reifier produces:
http-server://<host>:<port><base-path><verb-path>?httpMethodRestrict=<METHOD>
→ set-header Access-Control-* (if cors:)
→ process rest-path-extractor (if path has {placeholders})
→ set-header Content-Type: <produces>
→ target <verb.target>
So a verb with target: direct://getUser simply routes the HTTP
request to the workflow whose trigger is direct://getUser. That
workflow is responsible for the actual business logic.
Additionally, one route is registered per rest: block that serves
the generated OpenAPI spec:
http-server://<host>:<port>/openapi.json?httpMethodRestrict=GET
→ set-header Content-Type: application/json
→ process rest-openapi-handler-<base-path>
Composition example — REST front, agent back¶
rest:
- path: "/api/v1"
tag: "Support"
description: "Customer support API"
produces: "application/json"
cors:
allowed-origins: ["https://app.example.com"]
allowed-methods: ["POST"]
allowed-headers: ["Content-Type", "Authorization"]
max-age: 3600
security:
- type: bearer
scheme: "JWT"
verbs:
- method: post
path: "/chat"
target: direct://support-agent
description: "Send a message to the support agent"
request-type: "ChatRequest"
response-type: "ChatResponse"
workflows:
- id: support-agent
trigger: { uri: direct://support-agent }
auth:
type: jwt
jwt:
secret: "${JWT_SECRET}"
issuer: "https://auth.example.com"
audience: "support-api"
steps:
- agent:
id: support
type: react
model: { provider: openai, model: gpt-4o, api_key: "${OPENAI_API_KEY}" }
system_prompt: |
You are a helpful customer support agent. Answer questions
from the knowledge base when possible; escalate when not.
tools:
- name: search_kb
type: rag-query
description: "Search the knowledge base"
config: { knowledge_bases: ["support-docs"] }
GET http://0.0.0.0:8080/openapi.json returns:
{
"openapi": "3.0.3",
"info": { "title": "Helix REST API", "version": "1.0.0" },
"paths": {
"/api/v1/chat": {
"post": {
"tags": ["Support"],
"summary": "Send a message to the support agent",
"operationId": "post_api_v1_chat",
"requestBody": { ... },
"responses": { "200": { ... } },
"security": [{ "bearerAuth": [] }]
}
}
},
"tags": [{ "name": "Support", "description": "Customer support API" }],
"components": {
"securitySchemes": {
"bearerAuth": { "type": "http", "scheme": "JWT" }
}
}
}
Behaviour notes / gotchas¶
- Body handling is downstream's job. The REST DSL only routes the
HTTP request to the target URI. JSON parsing, validation, and
binding are the target route's responsibility (typically via a
transform: { starlang: 'read(payload, "application/json")' }step or anagent:step that does it implicitly). - No
OPTIONSshort-circuit. If you need CORS pre-flight, either declare anoptions:verb or run behind a CORS-aware gateway. - No body schema validation at REST layer.
request-type:andresponse-type:populate the OpenAPI spec but the engine doesn't enforce them. For runtime validation, drop avalidate:step in the target workflow. - Default ports. Block-level
portdefaults to8080. If you declare tworest:blocks with different ports you get two HTTP servers. - Path parameter type coercion.
data-type: integerproduces an integer in the OpenAPI spec, but the path extractor sets the captured value as a string header — coerce it downstream withint(headers["id"]).
See also¶
- Workflow DSL — what
target:URIs resolve to - OpenAPI generation — what
/openapi.jsonserves - http-server component — the trigger that backs each verb
- Schemas reference
Source¶
pkg/dsl/rest.go— the fluent Go builder (Rest(...).Get(...).To(...))pkg/dsl/rest_reifier.go— YAML rest block → runtime routespkg/dsl/rest_openapi.go— OpenAPI 3.0.3 generatorpkg/workflow/rest_definition.go— theRestDefinition/VerbDefinitionstructs