Skip to main content

Install

npm install @raily/sdk
import { Raily, RailyError, RailyAuthError } from "@raily/sdk";

Quickstart

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.

new Raily(options)

const client = new Raily({
  apiKey: "rly_...",        // required
  endpoint: "https://...",  // required — your endpoint URL
  timeoutMs: 60000,         // optional, milliseconds
});
OptionTypeDefaultDescription
apiKeystringThe API key for your endpoint.
endpointstringYour endpoint URL.
timeoutMsnumber60000Per-request timeout, in milliseconds.
Construct once and reuse it.

search(query, options?)

const results = await client.search("your search query", { limit: 5 });
ArgumentTypeDefaultDescription
querystringWhat to search for.
options.limitnumber3Max results (1–10). Fewer may come back.
options.explainbooleanfalseResolve 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

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:
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:
const client = new Raily({ apiKey: "rly_...", endpoint: "https://...", debug: true });
// console.warn(...) when a search returns nothing

SearchHit

interface SearchHit {
  score: number | null;
  fields: Record<string, string>; // your source's display fields (see below)
  text: string | null;            // a rendered text summary, when available
  image_url: string | null;       // the result's image, for image sources
  is_relevant: boolean | null;    // false for an exploratory (below-threshold) hit
  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:
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:
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 original structured payload (render types, individual items)? It’s on hit.raw.

Errors

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;
  }
}
ErrorWhenWhat to do
RailyAuthErrorKey missing, invalid, expired, or for a different endpoint.Check RAILY_API_KEY and that it was issued for this endpoint.
RailyErrorNetwork, 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:
const queries = ["batteries", "climate", "elections"];
const results = await Promise.all(queries.map((q) => client.search(q, { limit: 3 })));

Recipe: a search route

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 })));
}

Troubleshooting

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 scores above the relevance threshold. Search a specific topic ("battery recycling", not "articles").
  2. A freshness filter. Queries that imply recency ("latest", "news") apply a published_date cutoff — on an older corpus that can remove everything. Drop the recency words or widen the range.
  3. The corpus is empty. A brand-new source may have no documents indexed yet. Search a term you know is in the data; if that’s also empty, check ingestion/embedding.
Use { explain: true } to see which it is:
const res = await client.search("articles", { explain: true });
console.log(res.info.count, res.info.message, res.info.note);
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.
Raise timeoutMs and retry. Reuse one client instead of constructing one per request.