# http

> Expose functions as HTTP endpoints — registers the `http` trigger type and serves matched routes.

| field | value |
|-------|-------|
| version | 0.21.4 |
| type | binary |
| license | Apache-2.0 |
| repo | https://github.com/iii-hq/workers |
| supported_targets | x86_64-apple-darwin, aarch64-apple-darwin, i686-pc-windows-msvc, x86_64-pc-windows-msvc, aarch64-pc-windows-msvc, x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, x86_64-unknown-linux-musl, armv7-unknown-linux-gnueabihf |
| author | iii |

## installation

```sh
iii trigger compose::add worker=http@0.21.4
```

## configuration

```yaml
- concurrency_request_limit: 1024
  cors:
    allowed_methods:
      - GET
      - POST
      - PUT
      - DELETE
      - OPTIONS
    allowed_origins:
      - *
  default_timeout: 30000
  host: 127.0.0.1
  port: 3111
```

## dependencies

- `configuration` @ `0.x`

## readme

# http

Exposes registered functions as HTTP endpoints. Any function bound to an
`http` trigger becomes a route — external clients call it with a plain HTTP
request instead of going through the iii SDK, which is what you want for
webhooks, browser clients, or any caller outside the engine's WebSocket
protocol. Routing, path params, per-route/global middleware, conditional
execution, CORS, and chunked streaming responses are all handled by this
worker; the function just receives an `HttpRequest` and returns a value.

## Install

```bash
iii worker add http
```

`iii worker add` fetches the binary, writes a config block into
`~/.iii/config.yaml`, and the engine starts the worker the next time it boots.

## Quickstart

Register a function and bind it to this worker's trigger type (`http` — see
[Trigger type](#trigger-type)) with `api_path` and `http_method`:

```rust
use iii_sdk::protocol::RegisterTriggerInput;
use iii_sdk::{InitOptions, RegisterFunction, errors::Error, register_worker};
use iii_http::types::HttpRequest;
use serde_json::{json, Value};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let iii = register_worker("ws://localhost:49134", InitOptions::default());

    iii.register_function(
        "orders::get",
        RegisterFunction::new_async(|req: HttpRequest| async move {
            Ok::<Value, Error>(json!({
                "status_code": 200,
                "body": { "order_id": req.path_params.get("id") },
            }))
        }),
    );

    iii.register_trigger(RegisterTriggerInput {
        trigger_type: "http".to_string(),
        function_id: "orders::get".into(),
        config: json!({ "api_path": "/orders/:id", "http_method": "GET" }),
        metadata: None,
    })?;

    tokio::signal::ctrl_c().await?;
    Ok(())
}
```

```bash
curl http://localhost:3111/orders/42
# {"order_id":"42"}
```

The function's return value becomes the response: `status_code` (default
200), `headers`, and `body`. Path segments prefixed with `:` (e.g. `:id`)
land in `req.path_params`; query string and headers arrive as
`req.query_params` / `req.headers`.

## Configuration

| Field | Default | Description |
|---|---|---|
| `port` | `3111` | TCP port the HTTP server binds to. `0` binds an OS-assigned ephemeral port. |
| `host` | `0.0.0.0` | Host/interface to bind. |
| `default_timeout` | `30000` (ms) | Per-request timeout; on expiry the server returns `504`. |
| `cors.allowed_origins` | `[]` (permissive) | Allowed CORS origins. An empty list allows any origin. |
| `cors.allowed_methods` | `[]` (permissive) | Allowed CORS methods. An empty list allows any method. |
| `concurrency_request_limit` | `1024` | Maximum in-flight requests; requests over the limit wait for a slot. |
| `middleware[]` | `[]` | Global middleware, each `{ function_id, phase, priority }`, run on every route in ascending `priority` order before the handler. |

Configuration is owned by the `configuration` worker — edit it from the
console (**Configuration → Workers → http**) or seed it once via
`--config <file>.yaml` on first boot. `middleware` and `default_timeout`
hot-reload without a restart; `port`/`host`/`cors`/`concurrency_request_limit`
take effect on the next restart.

## Trigger type

This worker always registers the `http` trigger type. Bind a function to it
with:

| Field | Required | Default | Description |
|---|---|---|---|
| `api_path` | yes | — | Route path, e.g. `/orders/:id`. Segments prefixed with `:` are extracted into `path_params`. |
| `http_method` | no | `GET` | HTTP method to match. |
| `condition_function_id` | no | — | Function invoked first; if it returns a falsy value the request is rejected with `422`. |
| `middleware_function_ids` | no | `[]` | Per-route middleware, invoked before the handler in list order, in addition to any global middleware. |

Functions can stream their response: write to `req.response` (a
`StreamChannelRef`) with a `ChannelWriter` to send `set_status` /
`set_headers` control frames and body chunks for a chunked HTTP response.
Returning a non-null value instead yields a regular buffered response built
from `{ status_code, headers, body }`.

### Requires removing the legacy built-in HTTP service

The legacy built-in HTTP service also owns the `http` trigger type. Two owners
of the same trigger type on one engine collide — whichever registers last
wins — so this worker requires it to be absent: omit it from the
engine's `config.yaml` (a config that doesn't list a worker won't run it).

On boot, this worker queries the engine for connected workers and refuses to
start with a clear error if the legacy built-in is still active, so a stale config
fails loudly instead of silently racing the built-in worker for ownership of
`http`.

## skills

This version ships 1 skill file for agents, also browsable on the worker page:

- [`SKILL.md`](https://workers.iii.dev/workers/http?tab=skills&skill=SKILL.md&version=0.21.4)

### SKILL.md

# http

The `http` worker exposes registered functions as HTTP endpoints, turning a
function into a route that browsers, webhooks, and external clients can call
without using the iii SDK. It exposes no callable `http::*` functions; its
entire surface is the `http` trigger type, bound through a worker SDK trigger
registration such as `iii.registerTrigger({ type: 'http', function_id, config })`.

Install it with `iii worker add http`. The legacy in-engine HTTP service must not
run on the same engine because it also owns the `http` trigger type. Remove the
legacy built-in from the engine config before starting this worker; the standalone
worker refuses to boot when the builtin is active.

Incoming requests are matched by HTTP method and path. The invoked handler
receives method, path, headers, query params, extracted `:path` params, parsed
body, trigger metadata, and request/response stream channels. Returning a JSON
value yields a buffered response; writing to the response channel yields a
chunked streaming response.

## When to Use

- A webhook, browser, mobile app, or third-party service needs to call an iii
  function over plain HTTP.
- You need route matching with path params such as `/orders/:id`.
- A route should have per-route middleware or a condition function before the
  handler runs.
- You need CORS, HTTP status codes, headers, JSON bodies, or chunked streaming
  responses at the edge of the engine.
- Several external routes should dispatch into normal iii functions while
  keeping their handler code in workers you control.

## Boundaries

- No callable functions are exposed. Never invoke `http::*`; bind the `http`
  trigger type to one of your own functions.
- This is inbound HTTP. For outbound network requests from an agent or worker,
  use `web::fetch` instead.
- This is not a scheduler or event queue. For clock-based execution, use
  `cron`; for data-change reactions, use the relevant state, stream, queue, or
  database worker.
- The legacy in-engine HTTP service cannot run beside this standalone worker on
  the same engine because both own the `http` trigger type.
- Route shapes with the same method and equivalent path structure conflict:
  `/orders/:id` and `/orders/:order_id` are the same shape.

## Reactive triggers

Bind an `http` trigger when a handler should run automatically for each
matching HTTP request. The handler runs through the engine as a normal function
invocation, and the worker turns the handler's return value or stream writes
back into an HTTP response.

Reach for it when:

- A public or internal API endpoint should call a registered function.
- A webhook provider should invoke an iii workflow without a custom server.
- Middleware should authenticate, enrich, or reject requests before the route
  handler runs.
- A handler should stream chunks to the client instead of returning one
  buffered body.

### How to bind

1. Register a handler: `iii.registerFunction('orders::get', handler)`.
2. Register the trigger:

```typescript
iii.registerTrigger({
  type: 'http',
  function_id: 'orders::get',
  config: {
    api_path: '/orders/:id',
    http_method: 'GET',
    // condition_function_id and middleware_function_ids are optional.
  },
})
```

`api_path` is required. `http_method` defaults to `GET`. Path segments prefixed
with `:` are extracted into `path_params`; query string values arrive in
`query_params`; request headers arrive in `headers`.

Trigger config:

| Field | Required | Default | Description |
|---|---|---|---|
| `api_path` | yes | - | Route path such as `/orders/:id`; `:name` segments become path params. |
| `http_method` | no | `GET` | HTTP method to match. |
| `condition_function_id` | no | - | Function invoked before middleware and handler; a falsy result rejects the request with `422`. |
| `middleware_function_ids` | no | `[]` | Per-route middleware functions, run before the handler in list order. |

Handler return values become buffered responses with `status_code`, `headers`,
and `body`; `status_code` defaults to `200` and `body` defaults to an empty
object. For streaming, write `set_status` and `set_headers` control messages
plus body chunks to the request's `response` channel; return `null` or no final
buffered body from the handler.

Configure the server through the central configuration entry for worker
`http`:

```yaml
port: 3111
host: 0.0.0.0
default_timeout: 30000
concurrency_request_limit: 1024
cors:
  allowed_origins: []
  allowed_methods: []
middleware: []
```

Global `middleware` runs on every route in ascending `priority` order. Per-route
`middleware_function_ids` run after global middleware and before the handler.
`middleware` and `default_timeout` hot-reload; `port`, `host`, CORS, and
`concurrency_request_limit` take effect on the next restart.

## api reference

```json
{
  "functions": [
    {
      "description": "Internal: reload http configuration from the authoritative store on change.",
      "metadata": {},
      "name": "http::on-config-change",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "title": "ConfigChangeRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "ok"
        ],
        "title": "ConfigChangeAck",
        "type": "object"
      }
    }
  ],
  "triggers": [
    {
      "description": "HTTP API trigger",
      "invocation_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Configuration carried by an `http` trigger instance, as read from the trigger's `config` value.",
        "properties": {
          "api_path": {
            "type": "string"
          },
          "condition_function_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "http_method": {
            "default": "GET",
            "type": "string"
          },
          "middleware_function_ids": {
            "default": [],
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "api_path"
        ],
        "title": "HttpTriggerConfig",
        "type": "object"
      },
      "metadata": {},
      "name": "http",
      "return_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ChannelDirection": {
            "enum": [
              "read",
              "write"
            ],
            "type": "string"
          },
          "StreamChannelRef": {
            "properties": {
              "access_key": {
                "type": "string"
              },
              "channel_id": {
                "type": "string"
              },
              "direction": {
                "$ref": "#/definitions/ChannelDirection"
              }
            },
            "required": [
              "access_key",
              "channel_id",
              "direction"
            ],
            "type": "object"
          },
          "TriggerMetadata": {
            "description": "Metadata describing the trigger that produced an `HttpRequest`.",
            "properties": {
              "method": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "path": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "type": {
                "type": "string"
              }
            },
            "required": [
              "type"
            ],
            "type": "object"
          }
        },
        "description": "Request payload sent to the invoked function over the wire.",
        "properties": {
          "body": true,
          "headers": {
            "additionalProperties": {
              "type": "string"
            },
            "type": "object"
          },
          "method": {
            "type": "string"
          },
          "path": {
            "type": "string"
          },
          "path_params": {
            "additionalProperties": {
              "type": "string"
            },
            "type": "object"
          },
          "query_params": {
            "additionalProperties": {
              "type": "string"
            },
            "type": "object"
          },
          "request_body": {
            "$ref": "#/definitions/StreamChannelRef"
          },
          "response": {
            "$ref": "#/definitions/StreamChannelRef"
          },
          "trigger": {
            "anyOf": [
              {
                "$ref": "#/definitions/TriggerMetadata"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "body",
          "headers",
          "method",
          "path",
          "path_params",
          "query_params",
          "request_body",
          "response"
        ],
        "title": "HttpRequest",
        "type": "object"
      }
    }
  ]
}
```
