iii-http
v0.19.7-alpha.1Expose functions as HTTP endpoints.
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: 3111readme
open as markdowniii-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 (./data/configuration/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',
},
})