> ## Documentation Index
> Fetch the complete documentation index at: https://docs.raily.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript SDK

> Search your data from TypeScript or JavaScript with a typed client.

## Install

<CodeGroup>
  ```bash npm theme={null}
  npm install @raily/sdk
  ```

  ```bash pnpm theme={null}
  pnpm add @raily/sdk
  ```

  ```bash yarn theme={null}
  yarn add @raily/sdk
  ```
</CodeGroup>

```typescript theme={null}
import { Raily, RailyError, RailyAuthError } from "@raily/sdk";
```

## Quickstart

```typescript theme={null}
import { Raily } from "@raily/sdk";

const client = new Raily({
  apiKey: process.env.RAILY_API_KEY!,
  endpoint: process.env.RAILY_ENDPOINT!,
});

const results = await client.search("your search query", { limit: 5 });
for (const hit of results) {
  console.log(`${hit.score?.toFixed(2)}  ${hit.fields.title ?? "(untitled)"}`);
}
```

Get the endpoint URL and key from your endpoint's **API access** panel — see the
[SDK quickstart](/connect/sdk).

## `new Raily(options)`

```typescript theme={null}
const client = new Raily({
  apiKey: "rly_...",        // required
  endpoint: "https://...",  // required — your endpoint URL
  timeoutMs: 60000,         // optional, milliseconds
});
```

| Option      | Type     | Default | Description                           |
| ----------- | -------- | ------- | ------------------------------------- |
| `apiKey`    | `string` | —       | The API key for your endpoint.        |
| `endpoint`  | `string` | —       | Your endpoint URL.                    |
| `timeoutMs` | `number` | `60000` | Per-request timeout, in milliseconds. |

Construct once and reuse it.

## `search(query, options?)`

```typescript theme={null}
const results = await client.search("your search query", { limit: 5 });
```

| Argument          | Type      | Default | Description                                                            |
| ----------------- | --------- | ------- | ---------------------------------------------------------------------- |
| `query`           | `string`  | —       | What to search for.                                                    |
| `options.limit`   | `number`  | `3`     | Max results (up to 10; some endpoints cap lower). Fewer may come back. |
| `options.explain` | `boolean` | `false` | Resolve to a `SearchResult` (`hits` + `info`) instead of a bare array. |

Returns `Promise<SearchHit[]>` — or `Promise<SearchResult>` when `explain: true` (see
[Debugging empty results](#debugging-empty-results)).

### Debugging empty results

A search can legitimately return nothing — and a bare `[]` doesn't say why. Pass
`{ explain: true }` to get the endpoint's own message plus a hint:

```typescript theme={null}
const res = await client.search("articles", { explain: true });
res.info.count;    // 0
res.info.message;  // the endpoint's summary, e.g. "No results found"
res.info.note;     // why, and what to try (only set when there are no hits)
for (const hit of res.hits) {
  /* ... */
}
```

Or have the client log a one-line hint on every empty result during development:

```typescript theme={null}
const client = new Raily({ apiKey: "rly_...", endpoint: "https://...", debug: true });
// console.warn(...) when a search returns nothing
```

## SearchHit

```typescript theme={null}
interface SearchHit {
  score: number | null;
  fields: Record<string, string>; // your source's display fields (see below)
  text: string | null;            // a plain-text summary of the result, when available
  image_url: string | null;       // the result's image, for image sources
  is_relevant: boolean | null;    // false for a lower-confidence result (few strong matches)
  source_collection: string | null;
  id: string | null;
  raw: Record<string, unknown>;   // the full result, for anything not surfaced above
}
```

### Working with `fields`

`fields` is a flat `{ name: value }` map of the display fields configured on your source —
e.g. `title`, `text`, `author`, `published_date`. Read a value by name:

```typescript theme={null}
for (const hit of await client.search("your search query", { limit: 5 })) {
  console.log(hit.fields.title, "—", hit.fields.published_date);
}
```

A field with multiple values is joined with `", "`. To see what your source exposes, log
the whole map once:

```typescript theme={null}
const [first] = await client.search("your search query", { limit: 1 });
for (const [name, value] of Object.entries(first.fields)) {
  console.log(name, "=", value);
}
```

Need the full original result (every field, untrimmed)? It's on `hit.raw`.

## Errors

```typescript theme={null}
import { Raily, RailyError, RailyAuthError } from "@raily/sdk";

try {
  const hits = await client.search("your search query");
} catch (err) {
  if (err instanceof RailyAuthError) {
    // key missing, invalid, expired, or for a different endpoint
  } else if (err instanceof RailyError) {
    // network / server / timeout
  } else {
    throw err;
  }
}
```

| Error            | When                                                        | What to do                                                      |
| ---------------- | ----------------------------------------------------------- | --------------------------------------------------------------- |
| `RailyAuthError` | Key missing, invalid, expired, or for a different endpoint. | Check `RAILY_API_KEY` and that it was issued for this endpoint. |
| `RailyError`     | Network, server, or timeout.                                | Retry with backoff; raise `timeoutMs` for slow networks.        |

`RailyAuthError` extends `RailyError`, so check it first.

## Concurrency

`search` returns a promise — fan out with `Promise.all`:

```typescript theme={null}
const queries = ["batteries", "climate", "elections"];
const results = await Promise.all(queries.map((q) => client.search(q, { limit: 3 })));
```

## Recipe: a search route

<CodeGroup>
  ```typescript Next.js (app/api/search/route.ts) theme={null}
  import { Raily } from "@raily/sdk";

  const client = new Raily({
    apiKey: process.env.RAILY_API_KEY!,
    endpoint: process.env.RAILY_ENDPOINT!,
  });

  export async function GET(req: Request) {
    const q = new URL(req.url).searchParams.get("q") ?? "";
    const hits = await client.search(q, { limit: 5 });
    return Response.json(hits.map((h) => ({ score: h.score, fields: h.fields })));
  }
  ```

  ```typescript Express theme={null}
  import express from "express";
  import { Raily } from "@raily/sdk";

  const client = new Raily({
    apiKey: process.env.RAILY_API_KEY!,
    endpoint: process.env.RAILY_ENDPOINT!,
  });
  const app = express();

  app.get("/search", async (req, res) => {
    const hits = await client.search(String(req.query.q ?? ""), { limit: 5 });
    res.json(hits.map((h) => ({ score: h.score, fields: h.fields })));
  });
  ```
</CodeGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="No results came back (empty array)">
    An empty array is a valid answer, not an error. The usual causes, in order:

    1. **The query is too generic.** Category words like `"articles"`, `"news"`, or `"posts"`
       carry no topical signal, so nothing matches strongly enough to return. Search a
       *specific topic* (`"battery recycling"`, not `"articles"`).
    2. **A freshness filter.** Queries that imply recency (`"latest"`, `"news"`) apply a
       `published_date` cutoff — if your source's data is older, that can remove everything.
       Drop the recency words or widen the range.
    3. **Your source has no data yet.** A brand-new source may not have finished indexing yet.
       Search a term you *know* is in the data; if that's also empty, confirm the source has
       finished indexing in the Raily app.

    Use `{ explain: true }` to see which it is:

    ```typescript theme={null}
    const res = await client.search("articles", { explain: true });
    console.log(res.info.count, res.info.message, res.info.note);
    ```
  </Accordion>

  <Accordion title="“API key was rejected for this endpoint” (RailyAuthError)">
    The key is missing, wrong, expired, or was issued for a different endpoint. A key only
    works against the endpoint it was created on. Re-check `RAILY_API_KEY` and `RAILY_ENDPOINT`.
  </Accordion>

  <Accordion title="Searches are slow or time out">
    Raise `timeoutMs` and retry. Reuse one client instead of constructing one per request.
  </Accordion>
</AccordionGroup>
