# 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:

```typescript title="src/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:

```typescript
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

```typescript
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();
```

> [!NOTE]
> **React Apps**
> For React components, manage loading and error state yourself with `useState`, or use the pattern from [React Hooks](/frontend/react-hooks).

## HTTP Methods

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

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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.

> [!NOTE]
> **Validator Middleware**
> Use Hono's `zValidator` or `vValidator` middleware to get input type inference on the client side. Without a validator, request body types default to `unknown`.

## RPC Client vs React Hooks

| Use Case | Recommended |
|----------|-------------|
| React components | `hc()` plus `useState` or your preferred data library |
| Non-React apps (Vue, Svelte, vanilla JS) | `hc()` from `hono/client` |
| Server-side JavaScript | `hc()` from `hono/client` |
| CLI tools or scripts | `fetch` 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](/cookbook/patterns/server-utilities) for the complete pattern.

## Next Steps

- [React Hooks](/frontend/react-hooks): State-managed hooks for React apps
- [Provider Setup](/frontend/provider-setup): `AgentuityProvider` configuration for `@agentuity/react` apps
- [Adding Authentication](/frontend/authentication): Protect routes with Agentuity Auth
- [WebSockets](/routes/websockets): Create WebSocket routes
- [Server-Sent Events](/routes/sse): Create SSE routes