# How to Create Custom HTTP Endpoints with the iii-http Worker and Configure api_path

> Easily create custom HTTP endpoints with the iii-http worker. Learn how to bind iii functions to specific paths and methods using api_path for seamless request handling and response serialization.

- Repository: [iii/iii](https://github.com/iii-hq/iii)
- Tags: how-to-guide
- Published: 2026-05-28

---

**The iii-http worker exposes registered iii functions as REST endpoints by binding them to specific HTTP methods and URL paths via the `api_path` configuration, automatically handling request parsing and response serialization on port 3111.**

The **iii** repository provides a built-in HTTP server called the **iii-http** worker that transforms registered functions into RESTful APIs without requiring external web frameworks. When enabled in the default engine configuration found at [`engine/src/workers/rest_api/iii.worker.yaml`](https://github.com/iii-hq/iii/blob/main/engine/src/workers/rest_api/iii.worker.yaml), this worker runs an Axum server that routes incoming requests to your registered **http** triggers. Understanding how to register functions and properly configure the `api_path` parameter allows you to build scalable HTTP endpoints directly within the iii ecosystem.

## How the iii-http Worker Processes Requests

The iii-http worker acts as a bridge between raw HTTP traffic and your registered iii functions. According to the worker definition in [`engine/src/workers/rest_api/iii.worker.yaml`](https://github.com/iii-hq/iii/blob/main/engine/src/workers/rest_api/iii.worker.yaml), the server listens on **port 3111** by default and processes requests through a structured message-passing pipeline.

When an HTTP request arrives, the worker performs the following steps:

1. **Parses the request** to extract path parameters (declared as `:name` segments in `api_path`), query strings, headers, and body content
2. **Constructs an `HttpRequest` object** containing the extracted data in a structured format
3. **Routes to the registered function** associated with the matching `api_path` and HTTP method pair
4. **Serializes the returned `HttpResponse`** back to the client, including status codes, headers, and body content

The trigger system resolves routing conflicts by keeping the most recently registered route when duplicate `api_path` and `http_method` combinations exist.

## Registering a Custom HTTP Endpoint

Creating a functional endpoint requires three distinct operations: defining the handler function, registering it with the iii runtime, and binding it to an HTTP trigger configuration.

First, write a function that accepts an **`HttpRequest`** payload and returns an **`HttpResponse`** envelope. The handler extracts data from `request.path_params` or other request properties and returns a structured response object.

Second, register the function using your SDK's registration method, such as `iii.registerFunction` in JavaScript or `iii.register_function` in Python. Assign a unique **`function_id`** following the namespace convention (e.g., `greetings::hello`).

Third, create an HTTP trigger with `type: 'http'` that maps the function to a URL path. The `config` object must include:

- **`api_path`**: A string starting with `/` that defines the URL route (e.g., `/users/:id`)
- **`http_method`**: The HTTP verb (GET, POST, PUT, DELETE, etc.)

As documented in [`engine/src/workers/rest_api/skills/skills/http/reactive-triggers.md`](https://github.com/iii-hq/iii/blob/main/engine/src/workers/rest_api/skills/skills/http/reactive-triggers.md), the `api_path` supports dynamic segments using the `:parameter` syntax.

## Implementation Examples by SDK

### JavaScript and TypeScript

The Node SDK uses `iii.registerFunction` and `iii.registerTrigger` to set up endpoints. This example from [`sdk/packages/node/iii-example/src/index.ts`](https://github.com/iii-hq/iii/blob/main/sdk/packages/node/iii-example/src/index.ts) demonstrates a parameterized greeting endpoint:

```typescript
import { iii } from 'iii-sdk';

async function greet(req, logger) {
  const name = req.path_params?.name ?? 'world';
  return {
    status_code: 200,
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ message: `Hello, ${name}!` }),
  };
}

iii.registerFunction({ id: 'greetings::hello', handler: greet });

iii.registerTrigger({
  type: 'http',
  function_id: 'greetings::hello',
  config: { api_path: '/hello/:name', http_method: 'GET' },
});

```

### Python

In Python, use `iii.register_function` and `iii.register_trigger` with dictionary configurations. This pattern appears in [`sdk/packages/python/iii-example/src/main.py`](https://github.com/iii-hq/iii/blob/main/sdk/packages/python/iii-example/src/main.py):

```python
from iii import iii

def greet(request, logger):
    name = request.get('path_params', {}).get('name', 'world')
    return {
        'status_code': 200,
        'headers': {'Content-Type': 'application/json'},
        'body': f'{{"message": "Hello, {name}!"}}',
    }

iii.register_function({'id': 'greetings::hello', 'handler': greet})

iii.register_trigger({
    'type': 'http',
    'function_id': 'greetings::hello',
    'config': {'api_path': '/hello/:name', 'http_method': 'GET'},
})

```

### Rust

The Rust SDK provides typed builders for function and trigger registration. As shown in [`sdk/packages/rust/iii/tests/api_triggers.rs`](https://github.com/iii-hq/iii/blob/main/sdk/packages/rust/iii/tests/api_triggers.rs):

```rust
use iii_sdk::{iii, RegisterFunction, RegisterTrigger, HttpMethod, json};

fn greet(req: iii::HttpRequest) -> iii::HttpResponse {
    let name = req.path_params
        .get("name")
        .and_then(|v| v.as_str())
        .unwrap_or("world");
    iii::HttpResponse::new()
        .status_code(200)
        .header("Content-Type", "application/json")
        .body(json!({ "message": format!("Hello, {}!", name) }))
}

iii.register_function(
    RegisterFunction::new("greetings::hello", greet)
        .description("Greeting endpoint")
);

iii.register_trigger(
    iii::Trigger::Http {
        api_path: "/hello/:name".into(),
        http_method: HttpMethod::Get,
    }
    .for_function("greetings::hello")
);

```

## Configuring api_path and Path Parameters

The `api_path` configuration supports both static routes and dynamic path segments. When you define a segment like `:name` in the path string, the iii-http worker extracts the corresponding URL segment and populates `request.path_params` as a string key-value pair.

Key constraints from the reactive triggers documentation in [`engine/src/workers/rest_api/skills/skills/http/reactive-triggers.md`](https://github.com/iii-hq/iii/blob/main/engine/src/workers/rest_api/skills/skills/http/reactive-triggers.md):

- The `api_path` must begin with a leading slash (`/`)
- Path parameter names follow the colon immediately (e.g., `/users/:id/posts`)
- Extracted parameters appear in `path_params` as strings, requiring manual type conversion in your handler

You can also configure **optional middleware** via `middleware_function_ids` in the trigger configuration, or set **global middleware** in [`iii-config.yaml`](https://github.com/iii-hq/iii/blob/main/iii-config.yaml) to apply cross-cutting concerns like authentication.

## Worker Configuration Options

While the default configuration runs on port 3111, you can customize the iii-http worker behavior by modifying [`engine/src/workers/rest_api/iii.worker.yaml`](https://github.com/iii-hq/iii/blob/main/engine/src/workers/rest_api/iii.worker.yaml) or your local [`iii-config.yaml`](https://github.com/iii-hq/iii/blob/main/iii-config.yaml):

```yaml
name: iii-http
port: 3111
host: 0.0.0.0
default_timeout: 30000
concurrency_request_limit: 1024
body_limit: 1048576
trust_proxy: false
request_id_header: x-request-id
ignore_trailing_slash: false
not_found_function: myApp::fallback
cors:
  allowed_origins: ["*"]
  allowed_methods: ["GET", "POST"]

```

The **`not_found_function`** setting allows you to specify a custom function that receives unmatched requests, enabling single-page application serving or custom 404 handling.

## Summary

- The **iii-http worker** automatically exposes registered iii functions as REST endpoints when you configure HTTP triggers with `api_path` and `http_method` pairs.
- **Path parameters** use the `:name` syntax in `api_path` and appear as strings in `request.path_params` within your handler function.
- Registration requires three steps: define the handler function, register it with a unique ID, and create an HTTP trigger binding it to a specific path.
- The worker runs on **port 3111** by default, with configuration options available in [`engine/src/workers/rest_api/iii.worker.yaml`](https://github.com/iii-hq/iii/blob/main/engine/src/workers/rest_api/iii.worker.yaml) for port, host, CORS, request limits, and fallback handlers.
- SDKs for **JavaScript**, **Python**, and **Rust** provide equivalent registration APIs that follow the same underlying protocol defined in the reactive triggers documentation.

## Frequently Asked Questions

### What format should the api_path value use?

The `api_path` must be a string that starts with a forward slash (`/`) followed by the route definition. For dynamic segments, insert a colon followed by the parameter name, such as `/users/:id` or `/api/v1/resources/:resourceId`. This value is defined in the trigger configuration alongside the `http_method` property as documented in [`engine/src/workers/rest_api/skills/skills/http/reactive-triggers.md`](https://github.com/iii-hq/iii/blob/main/engine/src/workers/rest_api/skills/skills/http/reactive-triggers.md).

### How do I access URL parameters in my handler function?

Path parameters defined in `api_path` (e.g., `:name`) are extracted by the iii-http worker and placed into the **`path_params`** field of the `HttpRequest` object. In JavaScript, access them via `request.path_params.name`; in Python, use `request.get('path_params', {}).get('name')`; in Rust, access `req.path_params.get("name")`.

### Can I change the default port 3111?

Yes, modify the **`port`** field in the worker configuration file at [`engine/src/workers/rest_api/iii.worker.yaml`](https://github.com/iii-hq/iii/blob/main/engine/src/workers/rest_api/iii.worker.yaml) or override it in your [`iii-config.yaml`](https://github.com/iii-hq/iii/blob/main/iii-config.yaml). The default configuration binds to port 3111, but you can specify any available port for the Axum server to listen on.

### What happens if two triggers have the same api_path and method?

When conflicting routes exist with identical `api_path` and `http_method` values, the iii-http worker resolves the conflict by keeping the **most recently registered** trigger active. Earlier registrations for the same path and method combination are effectively overwritten by subsequent registrations.