---
title: "API and realtime"
description: "Typed oRPC calls from the Vue app, TanStack Query helpers, live streams, and the REST client."
canonical_url: "https://vinicunca.dev/taman/guides/api-and-realtime"
---
# API and realtime

> Typed oRPC calls from the Vue app, TanStack Query helpers, live streams, and the REST client.

## oRPC client

`apps/web/src/api/orpc.ts`:

```ts
import type { TamanContract } from '@taman/api-contract';
import type { ContractClient } from '@vinicunca/request/orpc';
import { createRpcClient } from '@vinicunca/request/orpc';
import { createRpcQueryUtils } from '@vinicunca/request/orpc-query';

export const client = createRpcClient<ContractClient<TamanContract>>({
  url: `${apiUrl.replace(/\/+$/, '')}/api/rpc`,
  credentials: 'include',
});

export const orpc = createRpcQueryUtils(client, { path: ['taman'] });
```

`createRpcClient` options (`@vinicunca/request/orpc`):

**url** (`string`) *required*: Full RPC endpoint, e.g. https://api.example.com/api/rpc.

**fetch** (`RpcFetch`): Override fetch (tests, SSR, Worker-to-Worker).

**headers** (`object | () => headers`): Extra headers per request (object or async function).

**credentials** (`RequestCredentials`): No default. Pass 'include' for cross-origin cookie auth.

Re-exported: `safe`, `isDefinedError`, `ORPCError`, `LIVE_RETRY`.

```ts
import { isDefinedError, safe } from '@vinicunca/request/orpc';

const [error, todo] = await safe(client.todo.get({ id }));
if (isDefinedError(error) && error.code === 'NOT_FOUND') {
  // typed contract error
}
```

## TanStack Query

```ts
import { useMutation, useQuery } from '@tanstack/vue-query';
import { orpc } from '#/api/orpc';

const list = useQuery(orpc.todo.list.queryOptions({
  input: { page: 1, pageSize: 20 },
}));

const create = useMutation(orpc.todo.create.mutationOptions({
  onSuccess: () => queryClient.invalidateQueries({ queryKey: orpc.todo.list.key() }),
}));
```

`createRpcQueryUtils` wraps `@orpc/tanstack-query`. The `path` option prefixes every query key (`['taman']` here). `queryClient` is the shared instance in `apps/web/src/query-client.ts` (also used by the router guard).

## Live streams

Contract: `todo.live` → `eventIterator(todoEventSchema)`. Server: `TodoService.subscribe` on channel `todo:${organizationId}`.

Reconnect after drops:

```ts
import { LIVE_RETRY } from '@vinicunca/request/orpc';

// pass context: LIVE_RETRY on the live call
```

`LIVE_RETRY` sets `retry: Infinity` and retries unless the error is an `ORPCError` with `status < 500`. CORS allows `Last-Event-ID`. Publisher: Durable Object on Cloudflare (`TODO_PUBLISHER`), `MemoryPublisher` in `nitro dev` (in-process only — multiple Node processes will not see each other's events). Resume retention is 300 seconds.

## REST client

Taman's product API is oRPC. `@vinicunca/request/http` is for **other** HTTP APIs. The web app's example is dummyjson:

```ts
import { createHttpClient } from '@vinicunca/request/http';
import { createHttpQueryUtils } from '@vinicunca/request/http-query';

export const dummyjsonClient = createHttpClient({ baseURL: 'https://dummyjson.com' });
export const dummyjson = createHttpQueryUtils(dummyjsonClient, { key: ['dummyjson'] });
```

Nothing is sent with cookies unless you pass `credentials: 'include'`. Install interceptors in this order when you add a real REST backend: **envelope → refresh token → error message**. See [@vinicunca/request](https://vinicunca.dev/taman/packages/request).

## Contract location

`packages/shared/api-contract` is the source of truth. Inputs/outputs: `TamanInputs`, `TamanOutputs`, `TamanContract`. Keep the web and API on the same workspace package so types never drift.


## Sitemap

See the full [sitemap](https://vinicunca.dev/sitemap.md) for all pages.
