iii-http
v0.21.0-next.1Expose functions as HTTP endpoints.
full markdown
/workers/iii-http.md?version=0.21.0-next.1. paste it into an llm prompt or pipe it through curl from a worker.install
configuration
- concurrency_request_limit: 1024
cors:
allowed_methods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
allowed_origins:
- *
default_timeout: 30000
host: 127.0.0.1
port: 3111dependencies
readme
iii-http
The HTTP Worker exposes registered functions as HTTP endpoints.
Install
iii worker add iii-httpResolves from the worker registry at workers.iii.dev.
Skills
Install the iii-http agent skill for Claude Code, Cursor, and 30+ other agents:
npx skills add iii-hq/iii --full-depth --skill iii-httpConfigure
Runtime settings live in the configuration worker under id iii-http. The worker registers its JSON Schema at startup, reads the live value via configuration::get (so ${VAR:default} placeholders in string fields expand against the process env), and hot-applies changes when the value updates.
The config.yaml block below is seed-only: it is installed as the initial value on first boot, when no value is stored yet. After that, the configuration worker entry is the source of truth — change settings via configuration::set or by editing the persisted file (./config/iii-http.yaml with the default fs adapter); both propagate without an engine restart. Edits to the config.yaml block are ignored once a value is stored.
Sample Seed Configuration
- name: iii-http
config:
port: 3111
host: 0.0.0.0
cors:
allowed_origins:
- http://localhost:3000
- http://localhost:5173
allowed_methods:
- GET
- POST
- PUT
- DELETE
- OPTIONSHot Reload
When the iii-http configuration changes, the worker re-reads the authoritative value and applies it in place:
cors,default_timeout,concurrency_request_limit, andmiddlewareare swapped without dropping the listener.- A
host/portchange binds the new address first and tears the old listener down only once the new one is live; in-flight requests on the old listener are aborted. If the new address cannot be bound, the change is rejected and the previous server keeps running. - Invalid values are rejected by schema validation at
configuration::settime; a stored value that fails to deserialize is logged and the previous config is kept.
Note: ${VAR:default} placeholders only work in string fields (e.g. host) — integer fields like port are validated as integers against the schema.
Configuration
| Field | Type | Description |
|---|---|---|
port |
number | The port to listen on. Defaults to 3111. |
host |
string | The host to listen on. Defaults to 0.0.0.0. |
default_timeout |
number | Default timeout in milliseconds for request processing. Defaults to 30000. |
concurrency_request_limit |
number | Maximum number of concurrent requests. Must be ≥ 1 (the schema rejects 0). Defaults to 1024. |
cors.allowed_origins |
string[] | Allowed CORS origins. An empty list allows any origin; list origins explicitly to restrict. |
cors.allowed_methods |
string[] | Allowed CORS methods. An empty list allows any method; list methods explicitly to restrict. |
middleware |
Middleware[] | Global middleware run on every route (see Middleware). |
Trigger Type: http
Register a trigger with type http to expose a function as an HTTP endpoint.
| Field | Type | Description |
|---|---|---|
api_path |
string | Required. The URL path. |
http_method |
string | Required. The HTTP method. |
condition_function_id |
string | Function ID for conditional execution. If it returns false, the handler is skipped. |
middleware_function_ids |
string[] | Per-route middleware function IDs, invoked in order before the handler. |
Sample Code
const fn = iii.registerFunction('api::getUsers', handler)
iii.registerTrigger({
type: 'http',
function_id: fn.id,
config: {
api_path: '/api/v1/users',
http_method: 'GET',
},
})Request & Response Objects
ApiRequest
| Field | Type | Description |
|---|---|---|
path |
string | The request path. |
method |
string | The HTTP method (e.g., GET, POST). |
path_params |
Record<string, string> | Variables extracted from the URL path (e.g., /users/:id). |
query_params |
Record<string, string> | URL query string parameters. |
body |
any | The parsed request body (JSON). |
headers |
Record<string, string> | HTTP request headers. |
trigger |
object | Trigger metadata: type, path, method. |
context |
object | Populated by middleware, available to handler functions. |
ApiResponse
| Field | Type | Description |
|---|---|---|
status_code |
number | HTTP status code. |
body |
any | The response payload. |
headers |
string[] | Record<string, string> | HTTP response headers as "Header-Name: value" strings or an object such as { "Content-Type": "application/json" }. Optional. |
Error Envelope
Errors the server generates itself (handler invocation failure, middleware failure or timeout, unmet route condition, route-miss 404s — including URLs that match no route at all — and response-stream build failures) use one stable JSON shape, so clients and AI agents can parse it without guessing:
{ "error": { "code": "HANDLER_ERROR", "message": "human-readable detail", "error_id": "a1b2c3d4e5f6" } }code— machine-readable identifier. Engine-generated codes includeMIDDLEWARE_TIMEOUT,CONDITION_NOT_MET,INTERNAL_ERROR,NOT_FOUND; handler/condition failures surface the function's own errorcode.message— human-facing detail.error_id— present on 5xx responses; correlates the response with server logs. Omitted where there is no log correlation (e.g. timeouts).- Unmet conditions return
422with"skipped": truealongside theerrorobject.
Bodies you return from your own handler or middleware pass through unchanged — the envelope only wraps errors the server raises.
Middleware
The HTTP module supports middleware functions that run before the handler.
- Per-route middleware — attached to a specific trigger via
middleware_function_ids - Global middleware — set in the
middlewarefield of theiii-httpconfiguration (seeded from the config.yaml block on first boot, hot-applied via the configuration worker afterwards), runs on all HTTP routes
Global Middleware Configuration
- name: iii-http
config:
port: 3111
middleware:
- function_id: "global::rate-limiter"
phase: preHandler
priority: 5
- function_id: "global::auth"
phase: preHandler
priority: 10| Field | Type | Description |
|---|---|---|
function_id |
string | Required. Function ID of the middleware to invoke. |
phase |
string | Lifecycle phase. Only preHandler is supported. Defaults to preHandler. |
priority |
number | Execution order. Lower values run first. Defaults to 0. |
Middleware Function Contract
Middleware functions receive a request object with path_params, query_params, headers, method (no body). They must return one of:
{ action: "continue" }— proceed to the next middleware or handler.{ action: "respond", response: { status_code, body, headers } }— short-circuit and return a response immediately.
Execution Order
1. Route match
2. Global middleware (from config, sorted by priority)
3. Condition check (if configured)
4. Per-route middleware (from trigger config, in order)
5. Body parsing
6. Handler functionExample Handler
import { registerWorker } from 'iii-sdk'
import type { ApiRequest, ApiResponse } from 'iii-sdk'
const iii = registerWorker('ws://localhost:49134')
async function getUser(req: ApiRequest): Promise<ApiResponse> {
const userId = req.path_params?.id
const user = await database.findUser(userId)
return {
status_code: 200,
body: { user },
headers: { 'Content-Type': 'application/json' },
}
}
const fn = iii.registerFunction('api::getUser', getUser)
iii.registerTrigger({
type: 'http',
function_id: fn.id,
config: {
api_path: '/users/:id',
http_method: 'GET',
},
})api reference (json)
{
"functions": [
{
"description": "Internal: re-apply the iii-http server configuration when the authoritative configuration entry changes.",
"metadata": {
"internal": true
},
"name": "iii-http::on-config-change",
"request_schema": {},
"response_schema": {}
}
],
"triggers": [
{
"description": "HTTP API trigger",
"invocation_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"HttpMethod": {
"enum": [
"GET",
"POST",
"PUT",
"DELETE",
"PATCH",
"HEAD",
"OPTIONS"
],
"type": "string"
}
},
"properties": {
"api_path": {
"description": "HTTP endpoint path (e.g. `/users/:id`)",
"type": "string"
},
"condition_function_id": {
"description": "Optional function ID to evaluate before invoking handler",
"type": [
"string",
"null"
]
},
"http_method": {
"anyOf": [
{
"$ref": "#/definitions/HttpMethod"
},
{
"type": "null"
}
],
"default": "GET",
"description": "HTTP method (defaults to GET)"
}
},
"required": [
"api_path"
],
"title": "HttpTriggerConfig",
"type": "object"
},
"metadata": {},
"name": "http",
"return_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"body": {
"description": "Request body"
},
"headers": {
"additionalProperties": {
"type": "string"
},
"description": "HTTP request headers",
"type": "object"
},
"method": {
"description": "HTTP method",
"type": "string"
},
"path": {
"description": "Request path",
"type": "string"
},
"path_params": {
"additionalProperties": {
"type": "string"
},
"description": "URL path parameters",
"type": "object"
},
"query_params": {
"additionalProperties": {
"type": "string"
},
"description": "URL query parameters",
"type": "object"
}
},
"required": [
"body",
"headers",
"method",
"path",
"path_params",
"query_params"
],
"title": "HttpCallRequest",
"type": "object"
}
}
]
}