> ## Documentation Index
> Fetch the complete documentation index at: https://iii.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Use Functions & Triggers

> Learn how to register Functions and Trigger them from anywhere within your backend across languages and from any service.

## Registering a Function and Triggering it

Function registration is just passing a function, an `id` for the function to `registerFunction(id, func)`
(or the equivalent in other languages). These functions can then be Triggered from anywhere else in the
application, and across language and service boundaries. Read more on that in the [Cross-language Triggering](#cross-language-triggering) section below.

Once registered, `math::add` is triggerable from anywhere in the system. This example also stores
each result in state so it can be aggregated later by a cron job.

<Tabs>
  <Tab title="Node / TypeScript">
    ```typescript title="math-add.ts" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    import { registerWorker, Logger } from 'iii-sdk';

    const iii = registerWorker(process.env.III_URL ?? 'ws://localhost:49134');

    iii.registerFunction(
      'math::add',
      async (input) => {
        const logger = new Logger();
        const result = input.a + input.b;
        const id = crypto.randomUUID();
        await iii.trigger({ function_id: 'state::set', payload: { scope: 'math', key: id, value: result } });
        logger.info('Math add completed', { id, result });
        return { id, result };
      },
      { description: 'Add two numbers and store result' },
    );

    await iii.trigger({ function_id: 'math::add', payload: { a: 2, b: 3 } });
    ```
  </Tab>

  <Tab title="Python">
    ```python title="math_add.py" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    import os
    import uuid

    from iii import Logger, register_worker

    iii = register_worker(os.environ.get("III_URL", "ws://localhost:49134"))


    def add(data):
        logger = Logger()
        result = data["a"] + data["b"]
        id = str(uuid.uuid4())
        iii.trigger({"function_id": "state::set", "payload": {"scope": "math", "key": id, "value": result}})
        logger.info("Math add completed", {"id": id, "result": result})
        return {"id": id, "result": result}


    iii.register_function("math::add", add)

    # Triggerable from any other function or worker
    iii.trigger({"function_id": "math::add", "payload": {"a": 2, "b": 3}})
    ```
  </Tab>

  <Tab title="Rust">
    ```rust title="math_add.rs" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    use iii_sdk::{register_worker, InitOptions, Logger, RegisterFunction, TriggerRequest};
    use serde_json::{json, Value};
    use uuid::Uuid;

    #[tokio::main]
    async fn main() -> Result<(), Box<dyn std::error::Error>> {
      let url = std::env::var("III_URL").unwrap_or_else(|_| "ws://127.0.0.1:49134".to_string());
      let iii = register_worker(&url, InitOptions::default());

      let iii_clone = iii.clone();
      let reg = RegisterFunction::new_async("math::add", move |input: Value| {
        let iii = iii_clone.clone();
        async move {
          let logger = Logger::new();
          let a = input["a"].as_i64().unwrap_or(0);
          let b = input["b"].as_i64().unwrap_or(0);
          let result = a + b;
          let id = Uuid::new_v4().to_string();

          iii.trigger(TriggerRequest { function_id: "state::set".into(), payload: json!({ "scope": "math", "key": id, "value": result }), action: None, timeout_ms: None }).await?;

          logger.info("Math add completed", Some(json!({ "id": id, "result": result })));
          Ok(json!({ "id": id, "result": result }))
        }
      }).description("Add two numbers and store result");
      iii.register_function(reg);

      // Triggerable from any other function or worker
      iii.trigger(TriggerRequest { function_id: "math::add".into(), payload: json!({ "a": 2, "b": 3 }), action: None, timeout_ms: None }).await?;
      Ok(())
    }
    ```
  </Tab>
</Tabs>

## HTTP-Invoked Functions

Instead of passing a handler, you can register an external HTTP endpoint as a function. The engine
makes the HTTP call when the function is triggered — no client-side HTTP code needed. This is useful
for delegating work to external services like webhooks, serverless functions, or third-party APIs.

<Warning title="Engine worker required">
  HTTP-invoked functions require the `iii-http-functions` worker to be enabled in your engine config.
  See the [engine configuration guide](./configure-engine) for details.
</Warning>

Pass an `HttpInvocationConfig` as the second argument to `registerFunction` instead of a handler:

<Tabs>
  <Tab title="Node / TypeScript">
    ```typescript title="http-invoked.ts" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    import { registerWorker } from 'iii-sdk';

    const iii = registerWorker(process.env.III_URL ?? 'ws://localhost:49134');

    iii.registerFunction(
      'notifications::send',
      {
        url: 'https://hooks.provider.example.com/notify',
        method: 'POST',
        timeout_ms: 5000,
        headers: { 'X-Service': 'iii-worker' },
        auth: {
          type: 'bearer',
          token_key: 'PROVIDER_API_TOKEN',
        },
      },
      { description: 'POST notification to Service Provider webhook' },
    );

    // Triggerable from any other function or worker
    await iii.trigger({ function_id: 'notifications::send', payload: { channel: '#alerts', text: 'Deploy succeeded' } });
    ```
  </Tab>

  <Tab title="Python">
    ```python title="http_invoked.py" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    from iii import HttpInvocationConfig, register_worker
    from iii.iii_types import HttpAuthBearer

    iii = register_worker("ws://localhost:49134")


    iii.register_function(
        "notifications::send",
        HttpInvocationConfig(
            url="https://hooks.provider.example.com/notify",
            method="POST",
            timeout_ms=5000,
            headers={"X-Service": "iii-worker"},
            auth=HttpAuthBearer(token_key="PROVIDER_API_TOKEN"),
        ),
        description="POST notification to Service Provider webhook",
    )

    iii.trigger({"function_id": "notifications::send", "payload": {"channel": "#alerts", "text": "Deploy succeeded"}})
    ```
  </Tab>

  <Tab title="Rust">
    ```rust title="http_invoked.rs" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    use iii_sdk::{register_worker, InitOptions, HttpAuthConfig, HttpInvocationConfig, HttpMethod, RegisterFunctionMessage, TriggerRequest};
    use std::collections::HashMap;

    let iii = register_worker("ws://localhost:49134", InitOptions::default());

    let mut headers = HashMap::new();
    headers.insert("X-Service".to_string(), "iii-worker".to_string());

    iii.register_function((
      RegisterFunctionMessage::with_id("notifications::send".into()).with_description("POST notification to Service Provider webhook".into()),
      HttpInvocationConfig {
        url: "https://hooks.provider.example.com/notify".to_string(),
        method: HttpMethod::Post,
        timeout_ms: Some(5000),
        headers,
        auth: Some(HttpAuthConfig::Bearer {
          token_key: "PROVIDER_API_TOKEN".to_string(),
        }),
      },
    ));

    iii.trigger(TriggerRequest { function_id: "notifications::send".into(), payload: json!({ "channel": "#alerts", "text": "Deploy succeeded" }), action: None, timeout_ms: None }).await?;
    ```
  </Tab>
</Tabs>

HTTP-invoked functions behave like any other function — they can be triggered with `trigger()`, bound
to any trigger type (queue, cron, state, etc.), and discovered by other services. The engine forwards
the trigger data as the JSON request body and treats non-2xx responses or network errors as failures.

### `HttpInvocationConfig` fields

| Field        | Type                     | Default  | Description                                            |
| ------------ | ------------------------ | -------- | ------------------------------------------------------ |
| `url`        | `string`                 | —        | The endpoint URL to call                               |
| `method`     | `string`                 | `"POST"` | HTTP method (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`)  |
| `timeout_ms` | `number`                 | `30000`  | Request timeout in milliseconds                        |
| `headers`    | `Record<string, string>` | —        | Additional headers to include                          |
| `auth`       | `HttpAuthConfig`         | —        | Authentication config (`bearer`, `hmac`, or `api_key`) |

<Info title="Auth values are environment variable names">
  Fields like `token_key`, `secret_key`, and `value_key` in `auth` config are environment variable
  names, not raw secrets. The engine resolves them from its process environment at invocation time.
</Info>

## Ways to Trigger Functions

As shown above functions can be triggered with `trigger({ function_id, payload })` but there are actually
three ways to trigger them and a way to register additional Triggers that fire Functions
according to internal and external events.

| Method                                                                        | Returns                | Use when                                                                                                                   |
| ----------------------------------------------------------------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `trigger({ function_id, payload })`                                           | The function's result  | You **need** the result                                                                                                    |
| `trigger({ function_id, payload, action: TriggerAction.Void() })`             | Nothing                | You **don't need** the result                                                                                              |
| `trigger({ function_id, payload, action: TriggerAction.Enqueue({ queue }) })` | `{ messageReceiptId }` | You want async processing with retries, concurrency control, and optional FIFO ordering                                    |
| `registerTrigger({ type, function_id, config })`                              | n/a                    | You need a function triggered as the result of another event such as: HTTP requests, Cron jobs, Queues, and State changes. |

### `trigger()` — Await the result

<Tabs>
  <Tab title="Node / TypeScript">
    ```typescript title="trigger.ts" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    iii.registerFunction('math::calculate', async (input) => {
      const logger = new Logger();
      const result = await iii.trigger({ function_id: 'math::add', payload: { a: input.a, b: input.b } })
      logger.info('Result', result) // { result: 5 }
      return result
    })
    ```
  </Tab>

  <Tab title="Python">
    ```python title="trigger.py" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    def calculate(data):
        result = iii.trigger({"function_id": "math::add", "payload": {"a": data["a"], "b": data["b"]}})
        print(result)  # {'result': 5}
        return result


    iii.register_function("math::calculate", calculate)
    ```
  </Tab>

  <Tab title="Rust">
    ```rust title="trigger.rs" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    use iii_sdk::RegisterFunction;
    use serde_json::Value;

    let iii_clone = iii.clone();
    let reg = RegisterFunction::new_async("math::calculate", move |input: Value| {
      let iii = iii_clone.clone();
      async move {
        let result = iii.trigger(TriggerRequest { function_id: "math::add".into(), payload: json!({"a": input["a"], "b": input["b"]}), action: None, timeout_ms: None }).await?;
        println!("{:?}", result); // {"result": 5}
        Ok(result)
      }
    });
    iii.register_function(reg);
    ```
  </Tab>
</Tabs>

### Fire-and-forget — `trigger` with `TriggerAction.Void()`

<Tabs>
  <Tab title="Node / TypeScript">
    ```typescript title="trigger-void.ts" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    import { TriggerAction } from 'iii-sdk'

    iii.registerFunction('math::calculate-async', async (input) => {
      iii.trigger({ function_id: 'math::add', payload: { a: input.a, b: input.b }, action: TriggerAction.Void() })
    })
    ```
  </Tab>

  <Tab title="Python">
    ```python title="trigger_void.py" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    from iii import TriggerAction


    def calculate_async(data):
        iii.trigger({"function_id": "math::add", "payload": {"a": data["a"], "b": data["b"]}, "action": TriggerAction.Void()})


    iii.register_function("math::calculate-async", calculate_async)
    ```
  </Tab>

  <Tab title="Rust">
    ```rust title="trigger_void.rs" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    use iii_sdk::{RegisterFunction, TriggerAction, TriggerRequest};
    use serde_json::Value;

    let iii_clone = iii.clone();
    let reg = RegisterFunction::new_async("math::calculate-async", move |input: Value| {
      let iii = iii_clone.clone();
      async move {
        iii.trigger(TriggerRequest {
          function_id: "math::add".into(),
          payload: json!({"a": input["a"], "b": input["b"]}),
          action: Some(TriggerAction::Void),
          timeout_ms: None,
        }).await?;
        Ok(serde_json::Value::Null)
      }
    });
    iii.register_function(reg);
    ```
  </Tab>
</Tabs>

### Enqueue — `trigger` with `TriggerAction.Enqueue({ queue })`

Enqueue work to a named queue for async processing. The engine acknowledges with `{ messageReceiptId }` when the job is accepted. The target function receives the payload when a worker processes it. Requires queues defined in `iii-config.yaml` — see [Use Named Queues](./use-named-queues).

<Tabs>
  <Tab title="Node / TypeScript">
    ```typescript title="trigger-enqueue.ts" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    import { TriggerAction } from 'iii-sdk'

    iii.registerFunction('orders::create', async (input) => {
      const order = { id: crypto.randomUUID(), ...input }
      const result = await iii.trigger({
        function_id: 'orders::process-order',
        payload: order,
        action: TriggerAction.Enqueue({ queue: 'payment' }),
      })
      return { orderId: order.id, messageReceiptId: result.messageReceiptId }
    })
    ```
  </Tab>

  <Tab title="Python">
    ```python title="trigger_enqueue.py" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    import uuid

    from iii import TriggerAction


    def create_order(input):
        order = {"id": str(uuid.uuid4()), **input}
        result = iii.trigger({
            "function_id": "orders::process-order",
            "payload": order,
            "action": TriggerAction.Enqueue(queue="payment"),
        })
        return {"orderId": order["id"], "messageReceiptId": result["messageReceiptId"]}


    iii.register_function("orders::create", create_order)
    ```
  </Tab>

  <Tab title="Rust">
    ```rust title="trigger_enqueue.rs" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    use iii_sdk::{RegisterFunction, TriggerAction, TriggerRequest};
    use serde_json::Value;

    let iii_clone = iii.clone();
    let reg = RegisterFunction::new_async("orders::create", move |input: Value| {
      let iii = iii_clone.clone();
      async move {
        let order_id = uuid::Uuid::new_v4().to_string();
        let order = json!({ "id": order_id, "items": input["items"], "total": input["total"] });
        let result = iii.trigger(TriggerRequest {
          function_id: "orders::process-order".into(),
          payload: order,
          action: Some(TriggerAction::Enqueue { queue: "payment".into() }),
          timeout_ms: None,
        }).await?;
        Ok(json!({ "orderId": order_id, "messageReceiptId": result["messageReceiptId"] }))
      }
    });
    iii.register_function(reg);
    ```
  </Tab>
</Tabs>

### `registerTrigger()` — Run on an event

Bind a Function to an event source. The engine triggers it automatically when the event fires. Below are
examples for common Trigger types: HTTP, Cron, and State.

#### HTTP

HTTP triggers receive an `ApiRequest` object with `body`, `query_params`, `path_params`, `headers`, and `method`.
The handler returns an `ApiResponse` with `status_code`, `body`, and optional `headers`.

<Tabs>
  <Tab title="Node / TypeScript">
    ```typescript title="http-trigger.ts" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    iii.registerFunction('math::multiply', async (req) => {
      const logger = new Logger();
      const { a, b } = req.body;
      const result = a * b;
      logger.info('Math multiply', { a, b, result });
      return {
        status_code: 200,
        body: { result },
        headers: { 'Content-Type': 'application/json' },
      };
    });

    iii.registerTrigger({
      type: 'http',
      function_id: 'math::multiply',
      config: { api_path: '/math/multiply', http_method: 'POST' },
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python title="http_trigger.py" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    def multiply(req):
        logger = Logger()
        a, b = req["body"]["a"], req["body"]["b"]
        result = a * b
        logger.info("Math multiply", {"a": a, "b": b, "result": result})
        return {
            "status_code": 200,
            "body": {"result": result},
            "headers": {"Content-Type": "application/json"},
        }


    iii.register_function("math::multiply", multiply)

    iii.register_trigger({
        "type": "http",
        "function_id": "math::multiply",
        "config": {"api_path": "/math/multiply", "http_method": "POST"},
    })
    ```
  </Tab>

  <Tab title="Rust">
    ```rust title="http_trigger.rs" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    use iii_sdk::RegisterFunction;
    use serde_json::Value;

    let reg = RegisterFunction::new_async("math::multiply", |req: Value| async move {
      let logger = Logger::new();
      let a = req["body"]["a"].as_i64().unwrap_or(0);
      let b = req["body"]["b"].as_i64().unwrap_or(0);
      let result = a * b;
      logger.info("Math multiply", Some(json!({ "a": a, "b": b, "result": result })));
      Ok(json!({
        "status_code": 200,
        "body": { "result": result },
        "headers": { "Content-Type": "application/json" }
      }))
    });
    iii.register_function(reg);

    iii.register_trigger(RegisterTriggerInput {
      trigger_type: "http".into(),
      function_id: "math::multiply".into(),
      config: json!({ "api_path": "/math/multiply", "http_method": "POST" }),
      metadata: None,
    })?;
    ```
  </Tab>
</Tabs>

#### Cron

This example aggregates all stored math results (from `math::add` above) every 30 minutes.

<Tabs>
  <Tab title="Node / TypeScript">
    ```typescript title="cron-trigger.ts" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    iii.registerFunction('math::aggregation', async () => {
      const logger = new Logger();
      const results = await iii.trigger({ function_id: 'state::list', payload: { scope: 'math' } });
      const values = results.filter((r) => typeof r === 'number');
      const sum = values.reduce((a, b) => a + b, 0);
      const aggregation = { count: values.length, sum, average: values.length ? sum / values.length : 0 };
      logger.info('Math aggregation completed', aggregation);
      return aggregation;
    });

    iii.registerTrigger({
      type: 'cron',
      function_id: 'math::aggregation',
      config: { expression: '0 */30 * * * * *' }, // every 30 minutes
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python title="cron_trigger.py" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    def aggregation(_):
        logger = Logger()
        results = iii.trigger({"function_id": "state::list", "payload": {"scope": "math"}})
        values = [r for r in results if isinstance(r, (int, float))]
        total = sum(values)
        agg = {"count": len(values), "sum": total, "average": total / len(values) if values else 0}
        logger.info("Math aggregation completed", agg)
        return agg


    iii.register_function("math::aggregation", aggregation)

    iii.register_trigger({
        "type": "cron",
        "function_id": "math::aggregation",
        "config": {"expression": "0 */30 * * * * *"},  # every 30 minutes
    })
    ```
  </Tab>

  <Tab title="Rust">
    ```rust title="cron_trigger.rs" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    use iii_sdk::RegisterFunction;
    use serde_json::Value;

    let iii_clone = iii.clone();
    let reg = RegisterFunction::new_async("math::aggregation", move |_: Value| {
      let iii = iii_clone.clone();
      async move {
        let logger = Logger::new();
        let results = iii.trigger(TriggerRequest { function_id: "state::list".into(), payload: json!({ "scope": "math" }), action: None, timeout_ms: None }).await?;
        let values: Vec<i64> = results.as_array()
          .map(|arr| arr.iter().filter_map(|r| r.as_i64()).collect())
          .unwrap_or_default();
        let sum: i64 = values.iter().sum();
        let count = values.len();
        let avg = sum as f64 / count as f64;
        logger.info("Math aggregation completed", Some(json!({ "count": count, "sum": sum, "average": avg })));
        Ok(json!({ "count": count, "sum": sum, "average": avg }))
      }
    });
    iii.register_function(reg);

    iii.register_trigger(RegisterTriggerInput {
      trigger_type: "cron".into(),
      function_id: "math::aggregation".into(),
      config: json!({ "expression": "0 */30 * * * * *" }), // every 30 minutes
      metadata: None,
    })?;
    ```
  </Tab>
</Tabs>

#### State

State triggers fire when a value in state changes. This example registers an external webhook
as an HTTP-invoked function and binds it to a state trigger. When the order status changes, the
engine POSTs the state event to the webhook URL automatically.

<Tabs>
  <Tab title="Node / TypeScript">
    ```typescript title="state-trigger.ts" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    iii.registerFunction(
      'orders::webhook',
      {
        url: process.env.WEBHOOK_URL!,
        method: 'POST',
        timeout_ms: 5000,
      },
    );

    iii.registerTrigger({
      type: 'state',
      function_id: 'orders::webhook',
      config: { scope: 'orders', key: 'status' },
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python title="state_trigger.py" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    from iii import HttpInvocationConfig

    iii.register_function(
        "orders::webhook",
        HttpInvocationConfig(url=os.environ["WEBHOOK_URL"], method="POST", timeout_ms=5000),
    )

    iii.register_trigger({
        "type": "state",
        "function_id": "orders::webhook",
        "config": {"scope": "orders", "key": "status"},
    })
    ```
  </Tab>

  <Tab title="Rust">
    ```rust title="state_trigger.rs" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    use iii_sdk::{register_worker, InitOptions, HttpInvocationConfig, HttpMethod, RegisterFunctionMessage, RegisterTriggerInput};
    use serde_json::json;
    use std::collections::HashMap;

    #[tokio::main]
    async fn main() -> Result<(), Box<dyn std::error::Error>> {
      let url = std::env::var("III_URL").unwrap_or_else(|_| "ws://127.0.0.1:49134".to_string());
      let iii = register_worker(&url, InitOptions::default());

      iii.register_function((
        RegisterFunctionMessage::with_id("orders::webhook".into()),
        HttpInvocationConfig {
          url: std::env::var("WEBHOOK_URL").expect("WEBHOOK_URL required"),
          method: HttpMethod::Post,
          timeout_ms: Some(5000),
          headers: HashMap::new(),
          auth: None,
        },
      ));

      iii.register_trigger(RegisterTriggerInput {
        trigger_type: "state".into(),
        function_id: "orders::webhook".into(),
        config: json!({ "scope": "orders", "key": "status" }),
        metadata: None,
      })?;

      Ok(())
    }
    ```
  </Tab>
</Tabs>

<Info title="Queue uses iii::durable::publish">
  Queue messaging uses `iii::durable::publish` — a built-in function, no trigger registration needed:
  Node `trigger({ function_id: 'iii::durable::publish', payload: { topic: 'user.created', data: {...} } })`,
  Python `trigger({'function_id': 'iii::durable::publish', 'payload': {'topic': 'user.created', 'data': {...}}})`,
  Rust `trigger(TriggerRequest::new("iii::durable::publish", json!({"topic": "user.created", "data": {...}})))`.
  See the [Queue worker](../workers/iii-queue) for details.
</Info>

## Trigger Types

| Type                 | Fires when                     | Config fields                        | Worker        |
| -------------------- | ------------------------------ | ------------------------------------ | ------------- |
| `http`               | HTTP request received          | `api_path`, `http_method`            | HTTP          |
| `cron`               | Schedule fires                 | `expression`                         | Cron          |
| `durable:subscriber` | Message published to a topic   | `topic`                              | Queue         |
| `subscribe`          | PubSub message on a topic      | `topic`                              | PubSub        |
| `state`              | State value changes            | `scope`, `key`                       | State         |
| `stream`             | Stream value changes           | `stream_name`, `group_id`, `item_id` | Stream        |
| `stream:join`        | Client connects to stream      | —                                    | Stream        |
| `stream:leave`       | Client disconnects from stream | —                                    | Stream        |
| `log`                | Log entry emitted              | `level`                              | Observability |

## Cross-language triggering

Any Function can be Triggered anywhere regardless of language. The engine handles serialization and routing:

<Tabs>
  <Tab title="Node / TypeScript">
    ```typescript title="cross-language.ts" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    iii.registerFunction('math::double', async (input) => {
      const logger = new Logger();
      // Trigger a function that might be implemented in Python, Rust, or any other language
      const result = await iii.trigger({ function_id: 'math::add', payload: { a: input.value, b: input.value } })
      logger.info('Result', result) // { result: 10 } if input.value was 5
      return result
    })
    ```
  </Tab>

  <Tab title="Python">
    ```python title="cross_language.py" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    def double(data):
        # Trigger a function that might be implemented in Node, Rust, or any other language
        result = iii.trigger({"function_id": "math::add", "payload": {"a": data["value"], "b": data["value"]}})
        print(result)  # {'result': 10} if value was 5
        return result


    iii.register_function("math::double", double)
    ```
  </Tab>

  <Tab title="Rust">
    ```rust title="cross_language.rs" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    use iii_sdk::RegisterFunction;
    use serde_json::Value;

    let iii_clone = iii.clone();
    let reg = RegisterFunction::new_async("math::double", move |input: Value| {
      let iii = iii_clone.clone();
      async move {
        // Trigger a function that might be implemented in Node, Python, or any other language
        let value = input["value"].as_i64().unwrap_or(0);
        let result = iii.trigger(TriggerRequest { function_id: "math::add".into(), payload: json!({"a": value, "b": value}), action: None, timeout_ms: None }).await?;
        println!("{:?}", result); // {"result": 10} if value was 5
        Ok(result)
      }
    });
    iii.register_function(reg);
    ```
  </Tab>
</Tabs>

The triggering Function doesn't know what language the target is written in or where it's running.

Once a Function is registered, every other part of the system can discover and trigger it. See [Discovery](../primitives-and-concepts/discovery) for how this works, including built-in functions the engine provides.

## Unregistering Functions and Triggers

Both `registerFunction` and `registerTrigger` return a reference with an `unregister()` method.
Calling it removes the registration from the engine so the function or trigger stops receiving invocations.

<Tabs>
  <Tab title="Node / TypeScript">
    ```typescript title="unregister.ts" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    const fn = iii.registerFunction('orders::create', async (input) => {
      return { status_code: 201, body: { id: '123', item: input.body.item } }
    })

    const trigger = iii.registerTrigger({
      type: 'http',
      function_id: 'orders::create',
      config: { api_path: '/orders', http_method: 'POST' },
    })

    fn.unregister()
    trigger.unregister()
    ```
  </Tab>

  <Tab title="Python">
    ```python title="unregister.py" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    def create_order(data):
        return {"status_code": 201, "body": {"id": "123", "item": data["body"]["item"]}}


    fn_ref = iii.register_function("orders::create", create_order)

    trigger = iii.register_trigger({
        "type": "http",
        "function_id": "orders::create",
        "config": {"api_path": "/orders", "http_method": "POST"},
    })

    fn_ref.unregister()
    trigger.unregister()
    ```
  </Tab>

  <Tab title="Rust">
    ```rust title="unregister.rs" theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
    use iii_sdk::RegisterFunction;
    use serde_json::Value;

    let reg = RegisterFunction::new_async("orders::create", |input: Value| async move {
        let item = input["body"]["item"].as_str().unwrap_or("");
        Ok(json!({ "status_code": 201, "body": { "id": "123", "item": item } }))
    });
    let fn_ref = iii.register_function(reg);

    let trigger = iii.register_trigger(RegisterTriggerInput {
        trigger_type: "http".into(),
        function_id: "orders::create".into(),
        config: json!({ "api_path": "/orders", "http_method": "POST" }),
        metadata: None,
    })?;

    fn_ref.unregister();
    trigger.unregister();
    ```
  </Tab>
</Tabs>

## registerWorker, Function, and Trigger Registration is Synchronous

The `registerWorker()` call returns immediately rather than returning a Promise. This is intentional.
Connection establishment happens asynchronously in the background.

This design avoids requiring developers to wrap initialization in an async function or await the SDK before
registering functions and triggers. Without this, every function and trigger definition would need to
wait on initialization, adding boilerplate to every file.

The trade-off: if code calls `shutdown()` immediately after `registerWorker()`, the connection may not yet
be established, resulting in an error like `WebSocket was closed before the connection was established`.
In practice this rarely matters — most applications register functions, respond to triggers, and run indefinitely.

## Function IDs

Function IDs use a `namespace::name` convention but can be any arbitrary string. iii conventions recommend
following this rule but the iii engine does not enforce it.

```
math::add
orders::process
notifications::send
```

<Warning title="iii prefix">
  The `iii::` prefix is reserved for internal engine functions. Function IDs cannot start with `iii::`.
</Warning>

## Built-in Functions

{/* TODO: Break these out into modules once those docs are ready and then have short higher level
mentions with one or two noted functions each and then link to the detailed docs */}

The engine provides built-in functions that can be triggered the same way any other Function would be triggered. These handle common operations like state management, streaming, messaging, and observability.

### State

Persistent key-value storage with scoped namespaces.

| Function             | Purpose                       | Parameters              |
| -------------------- | ----------------------------- | ----------------------- |
| `state::set`         | Store a value                 | `scope`, `key`, `value` |
| `state::get`         | Retrieve a value              | `scope`, `key`          |
| `state::delete`      | Remove a value                | `scope`, `key`          |
| `state::update`      | Atomic update with operations | `scope`, `key`, `ops`   |
| `state::list`        | List all values in a scope    | `scope`                 |
| `state::list_groups` | List all state scopes         | —                       |

### Stream

Real-time data streams with hierarchical organization.

| Function              | Purpose                       | Parameters                                            |
| --------------------- | ----------------------------- | ----------------------------------------------------- |
| `stream::set`         | Set a value in a stream       | `stream_name`, `group_id`, `item_id`, `data`          |
| `stream::get`         | Get a value from a stream     | `stream_name`, `group_id`, `item_id`                  |
| `stream::delete`      | Delete a value from a stream  | `stream_name`, `group_id`, `item_id`                  |
| `stream::update`      | Atomic update with operations | `stream_name`, `group_id`, `item_id`, `ops`           |
| `stream::list`        | List items in a group         | `stream_name`, `group_id`                             |
| `stream::list_groups` | List groups in a stream       | `stream_name`                                         |
| `stream::list_all`    | List all streams              | —                                                     |
| `stream::send`        | Send event to subscribers     | `stream_name`, `group_id`, `id`, `event_type`, `data` |

### Queue

Durable message queue for async processing.

| Function                | Purpose           | Parameters      |
| ----------------------- | ----------------- | --------------- |
| `iii::durable::publish` | Publish a message | `topic`, `data` |

### PubSub

Publish-subscribe messaging for event broadcasting.

| Function  | Purpose                         | Parameters      |
| --------- | ------------------------------- | --------------- |
| `publish` | Publish an event to subscribers | `topic`, `data` |

### Engine

Internal engine functions for introspection and management.

<Info title="Engine built-ins are always loaded">
  The engine built-ins are mandatory and always loaded.
</Info>

| Function                    | Purpose                                                                               | Parameters                                                                                                                  |
| --------------------------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `engine::functions::list`   | List all registered functions                                                         | `include_internal` (optional, default: `false`)                                                                             |
| `engine::workers::list`     | List connected SDK workers and in-process engine workers with metrics where available | `worker_id` (optional)                                                                                                      |
| `engine::triggers::list`    | List all triggers                                                                     | `include_internal` (optional, default: `false`)                                                                             |
| `engine::workers::register` | Register worker metadata (internal worker call)                                       | `_caller_worker_id`, `runtime` (optional), `version` (optional), `name` (optional), `os` (optional), `telemetry` (optional) |
| `engine::channels::create`  | Create a streaming channel pair                                                       | `buffer_size` (optional)                                                                                                    |

Built-in engine workers such as `iii-stream` and `iii-state` are included in worker list results. Rows with `internal: true` are mandatory engine internals and can be hidden in user-facing views.

### Observability

Logging, tracing, and metrics. The Observability worker is disabled by default and must be enabled in engine config.

| Function                   | Purpose                    | Parameters                                                                                                                                                                                                                                                                                                                                           |
| -------------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `engine::log::info`        | Log info message           | `message`, `data` (optional), `service_name` (optional), `trace_id` (optional), `span_id` (optional)                                                                                                                                                                                                                                                 |
| `engine::log::warn`        | Log warning message        | `message`, `data` (optional), `service_name` (optional), `trace_id` (optional), `span_id` (optional)                                                                                                                                                                                                                                                 |
| `engine::log::error`       | Log error message          | `message`, `data` (optional), `service_name` (optional), `trace_id` (optional), `span_id` (optional)                                                                                                                                                                                                                                                 |
| `engine::log::debug`       | Log debug message          | `message`, `data` (optional), `service_name` (optional), `trace_id` (optional), `span_id` (optional)                                                                                                                                                                                                                                                 |
| `engine::log::trace`       | Log trace message          | `message`, `data` (optional), `service_name` (optional), `trace_id` (optional), `span_id` (optional)                                                                                                                                                                                                                                                 |
| `engine::baggage::get`     | Get baggage item           | `key`                                                                                                                                                                                                                                                                                                                                                |
| `engine::baggage::set`     | Set baggage item           | `key`, `value`                                                                                                                                                                                                                                                                                                                                       |
| `engine::baggage::get_all` | Get all baggage items      | —                                                                                                                                                                                                                                                                                                                                                    |
| `engine::traces::list`     | List stored traces         | `trace_id` (optional), `service_name` (optional), `name` (optional), `status` (optional), `offset` (optional), `limit` (optional), `min_duration_ms` (optional), `max_duration_ms` (optional), `start_time` (optional), `end_time` (optional), `sort_by` (optional), `sort_order` (optional), `attributes` (optional), `include_internal` (optional) |
| `engine::traces::tree`     | Get trace tree             | `trace_id`                                                                                                                                                                                                                                                                                                                                           |
| `engine::traces::clear`    | Clear stored traces        | —                                                                                                                                                                                                                                                                                                                                                    |
| `engine::metrics::list`    | List current metrics       | `start_time` (optional), `end_time` (optional), `metric_name` (optional), `aggregate_interval` (optional)                                                                                                                                                                                                                                            |
| `engine::logs::list`       | List stored logs           | `start_time` (optional), `end_time` (optional), `trace_id` (optional), `span_id` (optional), `severity_min` (optional), `severity_text` (optional), `offset` (optional), `limit` (optional)                                                                                                                                                          |
| `engine::logs::clear`      | Clear stored logs          | —                                                                                                                                                                                                                                                                                                                                                    |
| `engine::health::check`    | System health status       | —                                                                                                                                                                                                                                                                                                                                                    |
| `engine::alerts::list`     | List alert states          | —                                                                                                                                                                                                                                                                                                                                                    |
| `engine::alerts::evaluate` | Manually evaluate alerts   | —                                                                                                                                                                                                                                                                                                                                                    |
| `engine::rollups::list`    | Get pre-aggregated metrics | `start_time` (optional), `end_time` (optional), `level` (optional), `metric_name` (optional)                                                                                                                                                                                                                                                         |
| `engine::sampling::rules`  | Get sampling rules         | —                                                                                                                                                                                                                                                                                                                                                    |
