Skip to content

SDKs and frameworks

Install @telemai/sdk, and your JavaScript or TypeScript code can search the web and read pages through Telem.

Using a coding agent? Find the one you use on Integrations.

@telemai/sdk is a typed TypeScript client for the Telem search API. It searches the web across providers and returns the results in the same shape for every provider. It also reads pages, groups calls into sessions, runs monitors, lists the providers, and sends query lineage for agents.

The package has no runtime dependencies. It uses the platform fetch, and you can supply your own. So the package runs in these places:

  • on Node
  • in an edge worker
  • inside another CLI
  • in tests
Terminal window
npm install @telemai/sdk

The package ships ESM and CommonJS builds with type definitions. Node 20.16 or later is necessary to read the credentials file. On other runtimes that have fetch, such as edge workers and browsers, pass the API key in code.

Create an API key under API keys in the Telem console. The guided installer writes it to ~/.telem/credentials.json. You can also set it as an environment variable, or pass it in code.

The client resolves apiKey and baseUrl in this order. The first value found wins:

  1. A constructor argument.
  2. The TELEM_API_KEY and TELEM_BASE_URL environment variables.
  3. ~/.telem/credentials.json.
  4. The defaults: no key, and https://router.telem.ai for baseUrl.

See API keys for the full rules.

import Telem from "@telemai/sdk"
const telem = new Telem() // environment, then ~/.telem/credentials.json
const withKey = new Telem("tlm_...") // just a key
const withOptions = new Telem({
apiKey: "tlm_...",
baseUrl: "https://router.telem.ai",
timeoutMs: 60_000, // per attempt
maxRetries: 2, // on 5xx, network errors and 429
tier: "extended", // client-wide search defaults go here
})

The credentials file is the only file the client opens. The client never reads .telem/telem.json, the file the other Telem tools use for search options. A library must not let a checked-out repository control the spend of a program. Set search defaults as constructor options (tier, fields, providersInclude, providersExclude, includeFullContent and autoRouting) or call arguments. The TELEM_* variables apply below them.

telem.providers() lists the search providers the deployment runs, as ProviderInfo objects with name, type, description, activeByDefault, tiers and autoRouted. autoRouted is true when auto-routing can choose the provider without a provider list, and null when the router does not say.

The client has no HTTP dependency, and it accepts your own fetch. This is how it runs inside another platform, in an edge worker, and in tests without a network:

const telem = new Telem({
apiKey: env.TELEM_API_KEY,
fetch: (input, init) => myFetch(input, init), // a worker, a proxy, a recording stub
})

Use Telem from JavaScript

const response = await telem.search("climate policy 2026", {
tier: "extended", // minimalist | default | extended | max
providersInclude: ["exa"], // omit to use the default provider set
numResults: 10, // rows per provider (server default 5)
})
for (const result of response.results) {
console.log(result.title, result.url)
}

response.results is one list across all providers, and response.byProvider keeps each provider’s run, failed ones included. Every option is on Search parameters.

Batches, the response, and every option

A batch of queries. Pass an array of queries. They run at the same time, as one request. Each provider run includes the query it served:

const response = await telem.search([
"EU AI Act obligations for general-purpose models 2026",
"how the amended EU AI Act timeline changed",
])
for (const run of response.byProvider) {
console.log(run.batchIndex, run.query, run.provider, run.results.length)
}

response.results is the flat list across every run. An array with one query behaves the same as a plain string.

The response.

response.results // flat SearchResult[]: providers in run order, rows in provider order
response.byProvider // ProviderRun[]: one entry per provider, failed providers included
response.sessionId // the session this call belongs to
response.interactionId // the id of this request
response.status // "succeeded" | "partially_succeeded" | "failed"

A provider that failed is still in byProvider, with status and error set and results empty. Each run has provider, status, results, error, latencyMs, preprocessorRunId, tier, fields, query and batchIndex, plus the provider-native answer, entities, related, verticals, usage, warnings and raw. Each result has url, title, summary, excerpt, fullContent, publishDate, rank, thumbnail, favicon, source, enrichments, fetchMeta, provider and raw. raw is the row exactly as the server sent it.

Every option, as JavaScript spells it. search() takes tier, fields, providersInclude, providersExclude, providerOverrides, numResults, includeRaw, includeFullContent, autoRouting, publishedAfter, publishedBefore, includeDomains, goal, context, session, metadata and signal.

  • An option you do not set uses the client default, then the server default. An undefined value is unset; [] and false are explicit values.
  • publishedAfter and publishedBefore take an ISO 8601 string or a Date. A Date is an exact instant. To include the whole day in publishedBefore, pass a "YYYY-MM-DD" string.
  • tier and fields do not go together, and the client sends one of them.
  • When you set both provider lists, the client removes the excluded names from the allow-list. If that leaves no provider, the call fails with a TelemError (code invalid_request) before anything is sent.
  • The client does not check other values. An unknown tier, field or provider name comes back as an error from the server. See Errors.
  • signal takes an AbortSignal. Aborting stops the request, and no retry follows.
  • numResults, includeRaw and providerOverrides are call arguments only. As client defaults they do nothing.

The client throws exactly two error classes. Branch on status and code. Do not branch on the message text:

import Telem, { TelemError, TimeoutError } from "@telemai/sdk"
try {
await telem.search("climate policy 2026")
} catch (error) {
if (error instanceof TimeoutError) {
// error.code === "timeout": one attempt took longer than timeoutMs
} else if (error instanceof TelemError) {
error.status // the HTTP status, or undefined when nothing was sent
error.code // "invalid_request" | "unsupported_server_version" | "connection_error", or a server code
error.details // the parsed response body, or the raw text
}
}
Situation Result
Bad input (empty query, empty URL list, a monitor with an agent and no ttl) TelemError, code: "invalid_request", nothing sent
Network failure TelemError, code: "connection_error", after the retries
5xx or 429 Retried with backoff (Retry-After applies), then TelemError with the status
Other 4xx TelemError with the status and the code from the server, not retried
Attempt longer than timeoutMs TimeoutError, not retried

monitors.create() retries only a 429. It does not retry a 5xx or a network failure: the server may already have created the monitor.

See Errors for every status code and what to do.

Was this page helpful?