# How JSON-RPC Methods Are Registered, Dispatched, and Schema-Documented in OpenHuman

> Discover how OpenHuman registers, dispatches, and documents JSON-RPC methods. Explore the domain-driven pattern, O(1) lookups, and schema endpoints at /_rpc/schemas.

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

---

**OpenHuman uses a domain-driven registration pattern where controllers are collected into a global registry in [`src/core/all.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/all.rs), dispatched via O(1) hash-map lookups in [`src/core/jsonrpc.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/jsonrpc.rs), and documented through aggregated schema endpoints exposed at `/_rpc/schemas`.**

OpenHuman implements a **JSON-RPC 2.0** API surface built on the Axum HTTP framework, utilizing a three-stage lifecycle for method handling. This architecture separates concerns between domain-specific controller definitions, centralized routing logic, and automatic schema generation to support both machine-readable APIs and human-friendly documentation.

## Domain-Driven Registration in [`src/core/all.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/all.rs)

Each functional domain—such as `voice`, `web_chat`, or `wallet`—exports two critical functions that enable the JSON-RPC system to discover its capabilities. The first function returns concrete handler implementations, while the second provides machine-readable documentation.

Domain modules expose these functions via `pub use` statements in their respective [`mod.rs`](https://github.com/tinyhumansai/openhuman/blob/main/mod.rs) files. For example, the voice domain in [`src/openhuman/voice/mod.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/voice/mod.rs) re-exports:

```rust
pub use schemas::{
    all_voice_controller_schemas,
    all_voice_registered_controllers,
    voice_schemas,
};

```

The central aggregator at [`src/core/all.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/all.rs) collects these vectors into a global registry:

```rust
let mut controllers = Vec::new();
controllers.extend(openhuman::voice::all_voice_registered_controllers());
controllers.extend(openhuman::web_chat::all_web_channel_registered_controllers());
// … repeat for each gated domain …

```

This pattern populates the **`REGISTERED_CONTROLLERS`** global registry, making every method available to the dispatcher at startup.

## O(1) Request Dispatch via [`src/core/jsonrpc.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/jsonrpc.rs)

Incoming HTTP POST requests hit the router constructed by `build_core_http_router` in [`src/core/jsonrpc.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/jsonrpc.rs). This Axum router defines two critical routes:

```rust
pub fn build_core_http_router() -> Router {
    Router::new()
        .route("/", post(handle_jsonrpc))
        .route("/_rpc/schemas", get(serve_schemas))
}

```

The `handle_jsonrpc` function executes the dispatch logic:

1. Parse the request body as a JSON-RPC envelope containing `jsonrpc: "2.0"`, `id`, `method`, and `params`.
2. Perform a **hash-map lookup** of the `method` string in `REGISTERED_CONTROLLERS`.
3. Invoke the stored handler with signature `fn(&Value) -> Result<Value, JsonRpcError>`.
4. Wrap the result or error into a proper JSON-RPC response.

Because the registry is built statically at startup, method resolution achieves **O(1) complexity** regardless of the total number of registered endpoints.

## Self-Documenting Schema Endpoints

OpenHuman automatically generates API documentation through the `/_rpc/schemas` endpoint, which aggregates schema definitions from every domain. The `serve_schemas` function in [`src/core/jsonrpc.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/jsonrpc.rs) assembles these into a single JSON array:

```rust
async fn serve_schemas() -> impl IntoResponse {
    let mut schemas = Vec::new();
    schemas.extend(openhuman::voice::all_voice_controller_schemas());
    schemas.extend(openhuman::web_chat::all_web_channel_controller_schemas());
    // … add each domain …
    Json(schemas)
}

```

Each `ControllerSchema` struct contains:
- **`method`** – The full JSON-RPC name (e.g., `openhuman.voice_stt_dispatch`).
- **`description`** – Human-readable purpose of the method.
- **`params`** – JSON Schema fragment for request validation.
- **`result`** – JSON Schema fragment for response structure.

The React frontend consumes this endpoint at startup to build the tool catalogue and populate the "RPC Explorer" interface, ensuring the UI always reflects the current API surface.

## Practical Example: Adding a New JSON-RPC Method

To expose a new capability through the JSON-RPC interface, you must register both the handler and its schema. Here is the complete workflow for adding a voice operation:

**1. Implement the controller** in the domain file (e.g., [`src/openhuman/voice/controllers.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/voice/controllers.rs)):

```rust
pub async fn my_new_action(params: Value) -> Result<Value, JsonRpcError> {
    // …implementation…
    Ok(json!({ "ok": true }))
}

```

**2. Register the handler** in the domain's registry (e.g., [`src/openhuman/voice/schemas/registry.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/voice/schemas/registry.rs)):

```rust
pub fn all_voice_registered_controllers() -> Vec<RegisteredController> {
    vec![
        RegisteredController::new(
            "voice.my_new_action",
            "Performs a custom voice operation",
            my_new_action,
        ),
    ]
}

```

**3. Define the schema** in the same registry file:

```rust
pub fn all_voice_controller_schemas() -> Vec<ControllerSchema> {
    vec![
        ControllerSchema::new(
            "voice.my_new_action",
            "Performs a custom voice operation",
            json!({ "type": "object", "properties": {} }),
            json!({ "type": "object", "properties": { "ok": { "type": "boolean" } } })
        ),
    ]
}

```

After recompiling, the method becomes immediately available for dispatch at the root RPC endpoint and automatically appears in the `/_rpc/schemas` documentation.

## Summary

- **Registration** occurs through domain-specific functions like `all_voice_registered_controllers()` that are aggregated in [`src/core/all.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/all.rs) into a global registry.
- **Dispatch** relies on O(1) hash-map lookups in [`src/core/jsonrpc.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/jsonrpc.rs), where `handle_jsonrpc` routes requests to the appropriate controller.
- **Schema Documentation** is served via the `/_rpc/schemas` endpoint, which merges `ControllerSchema` definitions from all domains to support automatic UI generation.
- The architecture decouples domain logic from transport concerns while maintaining type safety through Rust's strict function signatures.

## Frequently Asked Questions

### How does OpenHuman achieve O(1) routing for JSON-RPC methods?

OpenHuman builds a static hash map at startup in [`src/core/all.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/all.rs) by collecting all `RegisteredController` instances from domain modules. The `handle_jsonrpc` function in [`src/core/jsonrpc.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/jsonrpc.rs) performs a direct lookup of the method name string in this map, ensuring constant-time dispatch regardless of how many methods are registered.

### What is the difference between `all_voice_registered_controllers` and `all_voice_controller_schemas`?

`all_voice_registered_controllers` returns a vector of `RegisteredController` structs containing the actual Rust function pointers used for execution, while `all_voice_controller_schemas` returns `ControllerSchema` structs containing JSON Schema metadata for documentation and validation. Both must be updated when adding new methods to ensure the system can both execute and describe the endpoint.

### How can I expose a new domain's JSON-RPC methods in OpenHuman?

Create the domain's [`mod.rs`](https://github.com/tinyhumansai/openhuman/blob/main/mod.rs) file with two public functions following the naming convention `all_<domain>_registered_controllers()` and `all_<domain>_controller_schemas()`. Import and call these functions in [`src/core/all.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/all.rs) to append them to the global vectors, and the methods will automatically appear in both the dispatch registry and the schema documentation endpoint.

### Where does OpenHuman serve the JSON-RPC schema documentation?

The schema documentation is served at the `/_rpc/schemas` GET endpoint defined in [`src/core/jsonrpc.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/jsonrpc.rs). This endpoint aggregates all domain schemas into a single JSON array that clients—including the React frontend and CLI tools—consume to discover available methods and their parameter requirements.