RPC Client — Agentuity Documentation

RPC Client

Type-safe API calls from any JavaScript environment using hc() from hono/client

Call your API routes with full type safety using Hono's built-in RPC client. The type comes directly from your exported router, so there is no separate route registry to maintain.

Setup

1. Export your router type from your API file:

typescriptsrc/api/index.ts
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
import type { Env } from '@agentuity/runtime';
 
const router = new Hono<Env>()
  .post('/chat', zValidator('json', z.object({ message: z.string() })), async (c) => {
    const { message } = c.req.valid('json');
    return c.json({ response: `Echo: ${message}` });
  })
  .get('/users', async (c) => {
    return c.json([{ id: '1', name: 'Alice' }]);
  });
 
export default router;
 
// Export the type for use with hc()
export type ApiRouter = typeof router;

2. Create the client in your frontend:

import { hc } from 'hono/client';
import type { ApiRouter } from '../api/index';
 
const sameOriginClient = hc<ApiRouter>('/api');
const remoteClient = hc<ApiRouter>('https://your-project.agentuity.cloud/api');

Basic Usage

import { hc } from 'hono/client';
import type { ApiRouter } from '../api/index';
 
const client = hc<ApiRouter>('/api');
 
// POST with JSON body — fully typed from the zValidator schema
const res = await client.chat.$post({ json: { message: 'Hello' } });
const data = await res.json();
console.log(data.response);
 
// GET request
const usersRes = await client.users.$get();
const users = await usersRes.json();

HTTP Methods

Hono's RPC client uses $get, $post, $put, $delete, and $patch to match your route definitions:

import { hc } from 'hono/client';
import type { ApiRouter } from '../api/index';
 
const client = hc<ApiRouter>('/api');
 
const users = await (await client.users.$get()).json();
const created = await (await client.users.$post({ json: { name: 'Alice', email: 'alice@example.com' } })).json();
await client.users[':id'].$put({ param: { id: 'alice' }, json: { name: 'Alice Smith' } });
await client.users[':id'].$delete({ param: { id: 'alice' } });

WebSocket Connections

Use $ws() for WebSocket routes:

import { hc } from 'hono/client';
import type { ApiRouter } from '../api/index';
 
const client = hc<ApiRouter>('/api');
const ws = client.chat.$ws();
 
ws.addEventListener('open', () => {
  console.log('Connected');
  ws.send(JSON.stringify({ message: 'Hello!' }));
});
 
ws.addEventListener('message', (event) => {
  const data = JSON.parse(event.data);
  console.log('Received:', data);
});
 
ws.addEventListener('error', (event) => {
  console.error('WebSocket error:', event);
});
 
ws.close();

Server-Sent Events

For SSE routes, use the standard EventSource API since Hono's RPC client does not wrap SSE:

const events = new EventSource('/api/notifications');
 
events.addEventListener('message', (event) => {
  const data = JSON.parse(event.data);
  console.log('Event:', data);
});
 
events.addEventListener('error', () => {
  console.error('Stream error');
});
 
// Close when done
events.close();

If your SSE endpoint needs authentication, prefer same-origin cookies or a signed URL. The browser EventSource API does not let you attach arbitrary request headers.

Client Options

Pass a custom fetch or base URL when creating the client:

import { hc } from 'hono/client';
import type { ApiRouter } from '../api/index';
 
// With auth headers via a custom fetch wrapper
const client = hc<ApiRouter>('/api', {
  fetch: (input, init) =>
    fetch(input, {
      ...init,
      headers: {
        ...init?.headers,
        Authorization: `Bearer ${getToken()}`,
      },
    }),
});

Request Cancellation

Pass an AbortSignal through the fetch option:

import { hc } from 'hono/client';
import type { ApiRouter } from '../api/index';
 
const controller = new AbortController();
 
const client = hc<ApiRouter>('/api', {
  fetch: (input, init) =>
    fetch(input, { ...init, signal: controller.signal }),
});
 
const resultPromise = client.longProcess.$post({ json: { data: 'large payload' } });
 
// Cancel if needed
setTimeout(() => controller.abort(), 5000);
 
try {
  const result = await resultPromise;
} catch (error) {
  if (error instanceof Error && error.name === 'AbortError') {
    console.log('Request cancelled');
  }
}

Error Handling

The client returns Response objects — check the status yourself:

import { hc } from 'hono/client';
import type { ApiRouter } from '../api/index';
 
const client = hc<ApiRouter>('/api');
 
const res = await client.users.$post({ json: { name: 'Alice' } });
 
if (!res.ok) {
  const error = await res.json();
  if (res.status >= 400 && res.status < 500) {
    console.error('Client error:', res.status, error);
  } else {
    console.error('Server error:', res.status, error);
  }
} else {
  const user = await res.json();
  console.log('Created:', user);
}

Type Safety

The type for your router is derived directly from your route definitions — no code generation needed. Export the type from your API file and import it in your frontend:

import { zValidator } from '@hono/zod-validator';
import { Hono } from 'hono';
import { z } from 'zod';
 
const chatSchema = z.object({
  message: z.string(),
});
 
const router = new Hono()
  .post('/chat', zValidator('json', chatSchema), async (c) => {
    const { message } = c.req.valid('json');
    return c.json({ response: `Echo: ${message}` });
  });
 
export type ApiRouter = typeof router; // Captures all route types

The client infers input and output types from the router, so $post({ json: ... }) is fully typed without RPCRouteRegistry, generated route files, or a separate client schema.

RPC Client vs React Hooks

Use CaseRecommended
React componentshc() plus useState or your preferred data library
Non-React apps (Vue, Svelte, vanilla JS)hc() from hono/client
Server-side JavaScripthc() from hono/client
CLI tools or scriptsfetch directly

Calling Routes from External Backends

External backends like Next.js or Express can use HTTP to call Agentuity routes that access storage services. See SDK Utilities for External Apps for the complete pattern.

Next Steps