Browse documentation
TypeScript
A typed client on fetch — no dependencies, runs on the edge.
No package to install: the whole client is fetch and types, which means it runs unchanged
in Node, Deno, Bun, Cloudflare Workers and the browser (with CORS enabled for your origin).
Types
export interface Hit {
id: string;
url: string;
host: string;
title: string;
vertical: string;
snippet: string; // the passage text — usually the answer
score: number;
host_rank: number;
quality: number;
fetched_at: number;
source: "cc" | "delta";
story_id: string;
group_size?: number;
published_at?: number;
content_type?: string;
independent_sources?: number;
centrality?: number;
community_id?: string;
topics?: string[];
}
export interface SearchResponse {
vertical: string | null;
routed: boolean;
mode: "keyword" | "semantic" | "hybrid";
total: number;
results: Hit[];
facets?: Record<string, [string, number][]>;
/** True when part of the corpus was unreachable — the answer is INCOMPLETE. */
partial?: boolean;
}
export interface SearchParams {
q: string;
vertical?: string;
mode?: "keyword" | "semantic" | "hybrid";
lang?: string;
site?: string;
term?: string;
min_host_rank?: number;
min_independent_sources?: number;
min_centrality?: number;
/** IN-lists are comma-separated: "gov,edu". */
tld?: string;
topic?: string;
content_type?: string;
exclude_site?: string;
published_from?: number;
published_to?: number;
sort?: "relevance" | "recency" | "host_rank" | "quality" | "published" | "words" | "centrality";
collapse?: "none" | "host" | "page" | "story";
facets?: boolean;
fields?: string;
limit?: number;
}
The client
export class QuotaExhausted extends Error {}
export class Unlob {
private static RETRYABLE = new Set([408, 500, 502, 503, 504]);
constructor(
private key: string,
private base = "https://api.unlob.com",
) {}
private async get<T>(path: string, params: Record<string, unknown>, attempts = 4): Promise<T> {
const url = new URL(this.base + path);
for (const [k, v] of Object.entries(params)) {
if (v !== undefined && v !== null) url.searchParams.set(k, String(v));
}
for (let attempt = 0; attempt < attempts; attempt++) {
const res = await fetch(url, { headers: { "x-api-key": this.key } });
if (res.status === 429) {
const after = res.headers.get("retry-after");
if (after === null) {
// A 429 with no retry-after is a hard-capped monthly quota, not a rate
// limit. Retrying is a loop that cannot succeed.
throw new QuotaExhausted(await res.text());
}
await sleep(Number(after) * 1000);
continue;
}
if (Unlob.RETRYABLE.has(res.status) && attempt < attempts - 1) {
// Full jitter — clients that failed together must not retry together.
await sleep(Math.random() * 2 ** attempt * 1000);
continue;
}
if (!res.ok) throw new Error(`unlob ${res.status}: ${await res.text()}`);
return (await res.json()) as T;
}
throw new Error("exhausted retries");
}
search(params: SearchParams) {
return this.get<SearchResponse>("/search", params as Record<string, unknown>);
}
document(id: string) {
return this.get<Hit & { text: string }>(`/doc/${encodeURIComponent(id)}`, {});
}
corroborate(id: string, limit = 10) {
return this.get<{ independent_sources: number; merged_duplicates: number; sources: unknown[] }>(
"/corroborate", { id, limit });
}
assembleContext(q: string, budget = 4000) {
return this.get<{ items: { hit: Hit; reason: string }[]; estimated_tokens: number }>(
"/assemble_context", { q, budget });
}
}
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
Using it
const unlob = new Unlob(process.env.UNLOB_API_KEY!);
const body = await unlob.search({
q: "how does tokio schedule tasks",
vertical: "code",
collapse: "story",
limit: 5,
});
if (body.partial) {
// A 200 that means the answer is incomplete.
throw new Error("incomplete results — retry");
}
for (const hit of body.results) {
console.log(hit.title, hit.url, hit.independent_sources ?? 0);
console.log(hit.snippet);
}
In the browser
Cross-origin access is closed by default. If you need it from a browser origin, ask us to allow yours — and put the key on a server rather than in a bundle, since a key in client JavaScript is a public key.