Skip to content

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 via httpMethodRestrict=<METHOD>).
  • One GET /openapi.json route that serves a generated OpenAPI 3.0.3 spec for the entire rest: block.
  • Optional CORS pre-flight headers set as SetHeader steps before the forward to the verb's target.
  • Path-parameter extraction (e.g. /users/{id} → id is 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 an agent: step that does it implicitly).
  • No OPTIONS short-circuit. If you need CORS pre-flight, either declare an options: verb or run behind a CORS-aware gateway.
  • No body schema validation at REST layer. request-type: and response-type: populate the OpenAPI spec but the engine doesn't enforce them. For runtime validation, drop a validate: step in the target workflow.
  • Default ports. Block-level port defaults to 8080. If you declare two rest: blocks with different ports you get two HTTP servers.
  • Path parameter type coercion. data-type: integer produces an integer in the OpenAPI spec, but the path extractor sets the captured value as a string header — coerce it downstream with int(headers["id"]).

See also

Source

  • pkg/dsl/rest.go — the fluent Go builder (Rest(...).Get(...).To(...))
  • pkg/dsl/rest_reifier.go — YAML rest block → runtime routes
  • pkg/dsl/rest_openapi.go — OpenAPI 3.0.3 generator
  • pkg/workflow/rest_definition.go — the RestDefinition / VerbDefinition structs