# TypeScript SDK (/docs/sdk)



<Callout type="warn" title="Not on npm yet">
  `@relaygrid/sdk` is not published yet. Until it is, call the [REST API](/docs/api) with `fetch`
  (see the [first call](/docs/getting-started/first-call)). This page documents the client as it
  stands, so you know what is coming.
</Callout>

The SDK covers calls (one endpoint or a routed capability) and the live log. It runs anywhere with
`fetch` and web streams on the server: Node 20+, Bun, Deno and Workers. Never ship an API key to a
browser.

## Create a client [#create-a-client]

```ts
import { createGrid } from "@relaygrid/sdk";

const grid = createGrid({ apiKey: process.env.GRID_API_KEY! });
```

| Option      | Default                     |                                                                   |
| ----------- | --------------------------- | ----------------------------------------------------------------- |
| `apiKey`    | —                           | A key with `call` and `run` for calls, `logs:read` for the log    |
| `baseUrl`   | `https://api.gridrouter.io` |                                                                   |
| `fetch`     | global `fetch`              | Inject your own (tests, proxies)                                  |
| `userAgent` | —                           | Sent as `User-Agent`                                              |
| `app`       | —                           | Sent as `X-Grid-App`, so calls are attributed to your app in logs |
| `title`     | —                           | Sent as `X-Title`: your app's display name                        |

## Run a capability [#run-a-capability]

`run` lets GridRouter route: it tries the vendors for a capability in route order and stops at
the first hit. The body is the [run request](/docs/api/calls/run).

```ts
const result = await grid.run("people.email.find", {
  input: { first_name: "Ada", last_name: "Lovelace", domain: "stripe.com" },
  options: { cost: { max_cost_micro: 50_000 } },
});
console.log(result.provider, result.data);
```

## Call one endpoint [#call-one-endpoint]

`call` pins a vendor endpoint. Async endpoints return a `Job` to poll instead of a result.

```ts
const res = await grid.call("hunter/email.find", {
  input: { first_name: "Ada", last_name: "Lovelace", domain: "stripe.com" },
  include_raw: true,
});
```

## Stream the live log [#stream-the-live-log]

```ts
const tail = grid.logs.tail(
  {
    filter: { kinds: "call", capability: "people.email.find", status: "failed" },
    onControl: (frame) => console.error(frame.kind),
    onError: (err) => console.error(err),
  },
  (event) => {
    if (event.kind === "call") console.log(event.row.call_id, event.row.outcome);
  },
);

// later
tail.close();
await tail.done;
console.log(`dropped ${tail.dropped} events`);
```

`filter` takes the same keys as the [live tail](/docs/concepts/logs-and-drains#live-tail).
`tail` streams over SSE, reconnects with backoff from 0.5 s to 10 s, asks for `replay=100` on
reconnect and skips events it has already delivered. It stops on a 4xx other than 429 and reports
it to `onError`. Pass `signal` to stop with an `AbortSignal`, or `reconnect: false` to stop at the
first disconnect.

## Read recent events [#read-recent-events]

```ts
const { items } = await grid.logs.recent({ kinds: "call", limit: 20 });
```

`items` is newest last, up to 1,000 (default 50).

## Errors [#errors]

Failed requests throw `GridApiError`, which carries the [error envelope](/docs/errors)'s `code`,
`message` and HTTP `status`.

