| name | iii |
| description | | Use when this capability is needed. |
iii - Cross-Language Backend Engine
SDK 0.3.0-alpha Breaking Change (verified 2026-02-18): The registerTrigger field is trigger_type (NOT type). HTTP triggers use trigger_type: "api" (NOT "http"). Using type: "http" causes silent 404s on all routes. This was confirmed in the tac-4-go rewrite from motia to direct iii-sdk.
SDK Note: The iii.dev docs reference @iii-dev/sdk with a Bridge class — this package does not exist on npm (404 as of 2026-02-17). Always install iii-sdk (no scope). The docs SDK uses different conventions (dot separators, trigger_type, function_path). See references/cross-language.md for translation guide.
Core Concepts
iii has two fundamental primitives:
- Register - Make a function callable by the engine
- Call - Invoke any registered function regardless of language or process
Services connect to the iii engine over WebSocket. The engine handles routing, retries, observability, state, streams, cron, and HTTP API exposure.
Quick Start
1. Install the Engine
curl -fsSL https://install.iii.dev/iii/main/install.sh | sh
iii --version
Or run via Docker:
docker pull iiidev/iii:latest
docker run -p 3111:3111 -p 49134:49134 \
-v ./iii-config.yaml:/app/config.yaml:ro \
iiidev/iii:latest
2. Create Engine Config (iii-config.yaml)
modules:
- class: modules::api::RestApiModule
config:
port: 3111
host: 127.0.0.1
default_timeout: 30000
concurrency_request_limit: 1024
cors:
allowed_origins: ["*"]
allowed_methods: [GET, POST, PUT, DELETE, OPTIONS]
- class: modules::state::StateModule
config:
adapter:
class: modules::state::adapters::KvStore
config:
store_method: file_based
file_path: ./data/state_store.db
- class: modules::shell::ExecModule
config:
exec:
- bun run --enable-source-maps worker.js
See references/engine-modules.md for full module configuration including StreamModule, OtelModule, QueueModule, PubSubModule, CronModule, KvServer, LoggingModule, and BridgeClientModule.
3. Start the Engine
iii -c iii-config.yaml
4. Create a TypeScript Service
mkdir my-service && cd my-service
npm init -y
npm install iii-sdk
Set "type": "module" in package.json.
import { init, getContext } from "iii-sdk";
const { registerFunction, registerTrigger, call, callVoid } = init(
process.env.III_BRIDGE_URL ?? "ws://localhost:49134"
);
const health = registerFunction({ id: "my-service::health" }, async () => {
const { logger } = getContext();
logger.info("Health check OK");
return { status: 200, body: { healthy: true, timestamp: Date.now() } };
});
registerTrigger({
trigger_type: "api",
function_id: health.id,
config: { api_path: "health", http_method: "GET" },
});
console.log("Service started - listening for calls");
Run with: npx tsx src/worker.ts
Test: curl http://localhost:3111/health
Critical Rules
Always Do
- Use
service-name::function-name convention for function IDs (e.g., "client::health", "data-service::transform")
- Set
"type": "module" in package.json (SDK uses ESM)
- Use
process.env.III_BRIDGE_URL ?? "ws://localhost:49134" for the engine address
- Start the iii engine before starting services
- Use
getContext() for logging inside function handlers (provides structured OTEL logging)
- Use
Promise.allSettled() when calling multiple remote functions in parallel (graceful partial failure)
- Use
GET /health on port 3111 to check engine health before connecting services
- Use
index (not scope) for KV Server function parameters — scope is for the State module only
Never Do
- Never hardcode
ws://localhost:49134 without env var fallback
- Never use
process.env in function IDs (they must be static strings)
- Never assume all services are available - handle missing services gracefully
- Never use 6-field cron expressions - iii supports 7 fields (seconds included):
"*/30 * * * * * *"
- iii-sdk 0.3.0-alpha: Use
trigger_type (NOT type) in registerTrigger. Use "api" (NOT "http") for HTTP triggers. Use "queue" for queue triggers, "cron" for cron triggers
- iii-sdk 0.2.0: Use
type (NOT trigger_type). Use "http" for HTTP triggers
- Never import from
iii-sdk/state or iii-sdk/stream for basic state operations - use sdk.call("state::set", ...) instead. Removed in 0.3.0
- Never
npm install @iii-dev/sdk — this scoped package does not exist on npm (404). Use iii-sdk instead
- Never use dot separators in engine function paths — use
:: (double colon). "kv_server::get" not "kv_server.get"
SDK Entry Points
| Import | Purpose |
|---|
iii-sdk | Core: init, getContext, withContext, Logger (+ types). init() returns ISdk with registerFunction, registerTrigger, call, callVoid, shutdown methods |
iii-sdk/state | Advanced: Direct IState interface (get/set/delete/list/update) |
iii-sdk/stream | Advanced: Direct IStream interface with groups |
iii-sdk/telemetry | OpenTelemetry: initOtel, withSpan, getTracer, getMeter |
For most use cases, use the core SDK and call("state::set", ...) / call("state::get", ...) for state.
Function Registration
const fn = registerFunction(
{
id: "service::action",
description: "What it does",
request_format: { },
response_format: { },
metadata: { version: "1.0" },
},
async (payload) => {
const { logger } = getContext();
logger.info("Processing", { payload });
return { result: "done" };
}
);
Calling Functions
const result = await call<InputType, OutputType>("other-service::action", { data: "hello" });
callVoid("log-service::audit", { event: "user_login" });
const [a, b] = await Promise.allSettled([
call("service-a::process", payload),
call("service-b::process", payload),
]);
Built-in Functions
await call("state::set", { scope: "shared", key: "VERSION", value: 1 });
const val = await call("state::get", { scope: "shared", key: "VERSION" });
const items = await call("state::list", { scope: "shared" });
await call("kv_server::set", { index: "default", key: "user:123", value: { name: "Alice" } });
const user = await call("kv_server::get", { index: "default", key: "user:123" });
await call("kv_server::delete", { index: "default", key: "user:123" });
const keys = await call("kv_server::list_keys_with_prefix", { : });
(, { : , : { : , : } });
functions = (, {});
workers = (, {});
sdk.();
Note: shutdown() was added in 0.2.0. Always call it during graceful process termination.
Triggers
HTTP Trigger (API)
Exposes a function as an HTTP endpoint on the engine's REST API (default port 3111).
registerTrigger({
trigger_type: "api",
function_id: "service::handler",
config: {
api_path: "users/:id",
http_method: "POST",
},
});
Warning: Using type: "http" (0.2.0 syntax) with iii engine 0.4.0+ causes silent 404s on all routes. Always use trigger_type: "api" with 0.3.0-alpha SDK.
The handler receives an ApiRequest object:
registerFunction({ id: "service::handler" }, async (req: ApiRequest) => {
const { path_params, query_params, body, headers, method } = req;
return {
status_code: 200,
headers: { "Content-Type": "application/json" },
body: { id: path_params.id, data: body },
};
});
Cron Trigger
Supports 7-field cron expressions (seconds granularity):
registerTrigger({
trigger_type: "cron",
function_id: "service::cleanup",
config: { expression: "*/30 * * * * * *" },
});
Queue Trigger
Invokes a function when a message is enqueued to a specific topic. Requires QueueModule in engine config.
const consumer = registerFunction(
{ id: "events::on-user-created" },
async (data) => {
console.log("User created", data);
return { ok: true };
}
);
registerTrigger({
trigger_type: "queue",
function_id: consumer.id,
config: { topic: "user.created" },
});
await call("enqueue", { topic: "user.created", data: { id: "123", email: "user@example.com" } });
Log Trigger
Invokes a function when log events match a level. Requires LoggingModule in engine config.
registerFunction({ id: "monitoring::on-error" }, async (logEntry) => {
const { logger } = getContext();
logger.info("Error captured", logEntry);
});
registerTrigger({
trigger_type: "log",
function_id: "monitoring::on-error",
config: { level: "error" },
});
References
| Need | File |
|---|
| Full SDK type definitions and state/stream APIs | references/api-reference.md |
| Engine module configuration schemas | references/engine-modules.md |
| Cross-language examples, OpenTelemetry, env vars | references/cross-language.md |
| Docker deployment and project structure | references/docker-deployment.md |
Known Issues
- Alpha software: API may change between releases
- Port 49134 conflicts: The engine's WebSocket port is fixed at 49134; ensure nothing else uses it
- Docker networking: On Linux Docker <20.10,
host.docker.internal requires explicit extra_hosts mapping
- Reconnection: SDK auto-reconnects with exponential backoff (1s initial, 30s max, infinite retries by default)
- CJS support: 0.2.0 adds CommonJS exports alongside ESM - both
import and require now work
0.3.0-alpha Breaking Changes (confirmed working 2026-02-18)
These are current reality with iii-sdk@0.3.0-alpha.20260210122502 + iii engine 0.4.0:
trigger_type replaces type in registerTrigger — use trigger_type: "api" (NOT type: "http")
"api" replaces "http" as the HTTP trigger type name
iii-sdk/state export removed — use call("state::set", ...) and call("state::get", ...)
iii-sdk/stream renamed to iii-sdk/streams (plural)
ISdk type no longer exported from the main entry point
- SDK transition: The docs-only
@iii-dev/sdk (Bridge class, dot separators) may become the official SDK. Monitor npm for @iii-dev/sdk availability
Verified: tac-4-go project uses direct iii-sdk 0.3.0-alpha with trigger_type: "api" and all 4 HTTP endpoints + queue + cron work correctly.
Troubleshooting
| Error | Cause | Fix |
|---|
ECONNREFUSED 127.0.0.1:49134 | Engine not running | Start with iii -c iii-config.yaml |
| Function call times out (30s) | Target service not connected | Start the service; check engine::workers::list |
Cannot find module 'iii-sdk' | SDK not installed | npm install iii-sdk |
| HTTP endpoint returns 404 | Wrong trigger field or type | With 0.3.0-alpha: use trigger_type: "api" (NOT type: "http"). Verify api_path matches URL |
| Cron not firing | Wrong expression format | Use 7-field format: sec min hour dom mon dow year |
| State returns null | Wrong scope or key | Check scope/key strings match exactly |
trigger_type_not_found | SDK/engine version mismatch | 0.2.0: use type. 0.3.0-alpha: use trigger_type |
| All routes 404 (silent) | Used type: "http" with 0.3.0-alpha SDK | Change to trigger_type: "api" — motia sends "http", iii engine 0.4.0 expects "api" |
| KV Server timeout | KV Server module not in config | Add modules::kv_server::KvServer to iii-config.yaml |
module class not found | Wrong singular/plural path | Try stream vs streams in class path; check engine version |
@iii-dev/sdk npm 404 | Package not published | Use iii-sdk (no scope). @iii-dev/sdk is docs-only |
| Queue messages not processing | Missing queue trigger or wrong topic | Verify registerTrigger({ type: "queue", config: { topic } }) matches enqueue topic |
Source: evolv3-ai/vibe-skills — distributed by TomeVault.