# OpenHuman JSON-RPC Dispatch Flow: From HTTP Request to Domain Handler

> Explore the OpenHuman JSON-RPC dispatch flow. See how HTTP requests are handled validated and routed to domain handlers via Axum and static schemas.

- Repository: [Tiny Humans/openhuman](https://github.com/tinyhumansai/openhuman)
- Tags: internals
- Published: 2026-05-12

---

**OpenHuman processes JSON-RPC requests through a layered Axum handler that validates parameters against static schemas before dispatching to registered domain-specific async functions.**

The OpenHuman framework exposes its functionality via JSON-RPC 2.0 over HTTP, routing requests through a type-safe dispatch pipeline implemented in Rust. This architecture separates HTTP handling from business logic by using a static controller registry to validate and route calls to domain handlers located throughout the codebase.

## HTTP Entry Point and Axum Routing

OpenHuman receives JSON-RPC calls on the `POST /rpc` endpoint defined in [`src/core/jsonrpc.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/jsonrpc.rs). The **Axum** web framework binds this route to the `rpc_handler` function, which acts as the primary ingress point for all remote procedure calls.

When a request arrives, `rpc_handler` extracts the JSON-RPC envelope containing the `id`, `method`, and `params` fields. It then immediately delegates to the internal dispatch machinery while preparing to encode the final response as a JSON-RPC 2.0 object.

## The Session-Aware Dispatch Pipeline

The dispatch flow proceeds through two distinct layers to handle cross-cutting concerns:

1. **`invoke_method`** – This wrapper function in [`src/core/jsonrpc.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/jsonrpc.rs) adds session-management logic, including automatic clearing of 401 Unauthorized tokens. It sanitizes the execution context before passing control to the core dispatcher.

2. **`invoke_method_inner`** – Also defined in [`src/core/jsonrpc.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/jsonrpc.rs), this function performs the actual method resolution and invocation. It first queries the static controller registry via `all::schema_for_rpc_method` to locate the appropriate handler for the requested method name.

If the registry contains a matching schema, `invoke_method_inner` converts the parameters to an object, validates them against the schema, and invokes the method through `all::try_invoke_registered_rpc`. If no handler exists in the registry, the call falls back to the legacy shim `crate::core::dispatch::dispatch` in [`src/rpc/dispatch.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/rpc/dispatch.rs), which currently returns `None` to indicate method not found.

## Static Controller Registry and Schema Validation

The **static controller registry** in [`src/core/all.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/all.rs) serves as the authoritative source of truth for all available RPC methods. During application startup, `all_registered_controllers()` aggregates schemas from every domain module and registers the concrete handler functions.

Each domain (such as `openhuman::cron` or `openhuman::credentials`) provides an [`rpc.rs`](https://github.com/tinyhumansai/openhuman/blob/main/rpc.rs) file implementing the business-logic function and registers it with the central registry. For example, in [`src/openhuman/cron/rpc.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/cron/rpc.rs), a handler might look like:

```rust
pub async fn schedule_job(params: Map<String, Value>) -> Result<Value, String> {
    // Domain logic delegates to crate::openhuman::cron::ops::schedule(...)
}

```

The registration process maps method names (like `openhuman.cron_schedule_job`) to their corresponding async functions and input schemas, enabling compile-time validation of the RPC interface.

## Domain Handler Execution

Once the registry resolves a method, the corresponding async function executes within the Tokio runtime. The handler typically follows a three-step pattern:

- **Parameter extraction** – Parsing the validated `serde_json::Map<String, Value>` into domain-specific structs
- **Business logic delegation** – Calling functions in the domain's [`ops.rs`](https://github.com/tinyhumansai/openhuman/blob/main/ops.rs) module (e.g., [`src/openhuman/cron/ops.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/cron/ops.rs)) to perform database updates or external API calls
- **Result serialization** – Returning a `serde_json::Value` that becomes the `result` field in the JSON-RPC response

After the domain handler completes, the result propagates back up through `invoke_method` to `rpc_handler`, which wraps successful returns in an `RpcSuccess` object or errors in an `RpcFailure` before encoding the HTTP response.

## Code Examples

### Making an HTTP JSON-RPC Request

You can invoke the dispatch flow directly via curl:

```bash
curl -s http://127.0.0.1:7788/rpc \
  -H "Content-Type: application/json" \
  -d '{
        "jsonrpc":"2.0",
        "id":1,
        "method":"openhuman.cron_schedule_job",
        "params":{"cron":"0 * * * *","name":"hourly_cleanup"}
      }'

```

This request enters through `rpc_handler` in [`src/core/jsonrpc.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/jsonrpc.rs), validates against the cron schema in the registry, and executes the `schedule_job` function in [`src/openhuman/cron/rpc.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/cron/rpc.rs).

### Calling the Dispatch Flow from Rust

For internal calls or testing, bypass HTTP and invoke the dispatcher directly:

```rust
use openhuman_core::core::jsonrpc::{invoke_method, default_state};

#[tokio::main]
async fn main() {
    let state = default_state();
    let method = "openhuman.cron_schedule_job";
    let params = serde_json::json!({
        "cron": "0 * * * *",
        "name": "hourly_cleanup"
    });

    match invoke_method(state, method, params).await {
        Ok(value) => println!("Result: {}", value),
        Err(err) => eprintln!("RPC error: {}", err),
    }
}

```

This uses the same validation and registry lookup as HTTP requests but skips the network layer.

### Adding a New Domain Method

To extend the dispatch flow with new functionality:

1. Create `src/openhuman/<domain>/rpc.rs` with an async function accepting `Map<String, Value>` and returning `Result<Value, String>`
2. Define the input schema in `src/openhuman/<domain>/schemas.rs`
3. Register the method in [`src/core/all.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/all.rs) via `register_controller` to populate the static registry at startup
4. Implement core logic in `src/openhuman/<domain>/ops.rs`

Once registered, the method becomes available via `/rpc` without additional routing changes.

## Summary

- **HTTP handling** occurs in `rpc_handler` within [`src/core/jsonrpc.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/jsonrpc.rs), which parses the JSON-RPC envelope.
- **`invoke_method`** manages sessions and wraps **`invoke_method_inner`**, the core dispatcher.
- **Static controller registry** ([`src/core/all.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/all.rs)) validates parameters via schemas and resolves method names to handler functions.
- **Domain handlers** (e.g., [`src/openhuman/cron/rpc.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/cron/rpc.rs)) execute business logic and return serialized results.
- **Legacy fallback** routes to [`src/rpc/dispatch.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/rpc/dispatch.rs) when methods are missing from the registry.
- **Response encoding** wraps results in JSON-RPC 2.0 response objects before returning to the client.

## Frequently Asked Questions

### How does OpenHuman validate JSON-RPC parameters?

OpenHuman validates parameters through its static controller registry in [`src/core/all.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/all.rs). When `invoke_method_inner` processes a request, it retrieves the schema for the requested method via `all::schema_for_rpc_method` and validates the input parameters against this schema before invocation. This ensures type safety and required field checking before the domain handler executes.

### What happens if a method is not found in the controller registry?

If `all::schema_for_rpc_method` returns no matching entry, the dispatch flow falls back to the legacy compatibility shim `crate::core::dispatch::dispatch` located in [`src/rpc/dispatch.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/rpc/dispatch.rs). Currently, this fallback returns `None`, which results in a method-not-found error response being generated by the `rpc_handler`.

### Where is the HTTP router configured in OpenHuman?

The HTTP router is constructed in the `build_core_http_router` function within [`src/core/jsonrpc.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/jsonrpc.rs). This function sets up the Axum routes, including binding the `POST /rpc` endpoint to the `rpc_handler` function, and applies any middleware layers before the router begins accepting connections.

### How do I register a new JSON-RPC method in OpenHuman?

To register a new method, implement the handler function in your domain's [`rpc.rs`](https://github.com/tinyhumansai/openhuman/blob/main/rpc.rs) file, define its schema in [`schemas.rs`](https://github.com/tinyhumansai/openhuman/blob/main/schemas.rs), and add a registration entry in [`src/core/all.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/all.rs) using `register_controller`. This entry populates the static registry during startup, making the method available through the standard dispatch flow without modifying the HTTP routing logic.