Seltz
Guide

Guide · 9 min read · 2026-06-04

Build a GTM intelligence agent with Seltz.

Type in one company domain, and a minute or two later you get back a scored list of the accounts that company should be selling to, the strongest of them with decision-makers attached and a real source behind every company and contact. The profiling, the scoring, and the judgment are a language model. The part that finds the real companies and people out on the live web is Seltz.

By William EspegrenSeltz / GTM

A go-to-market tool has two hard problems folded into it. One is judgment: given a company, working out who it should be selling to, and then looking at a specific account and deciding whether it is actually a good fit. A capable model is genuinely good at that kind of reasoning. The other problem is finding those accounts and the people inside them, and here a model on its own is stuck. It cannot see a company founded last quarter or a funding round announced this week, because its training data froze a long time before you asked.

This post walks through a demo that keeps those two jobs apart. The reasoning runs on a language model, and you can pick which one. The finding runs on Seltz, a web knowledge API that returns clean, source-backed documents. The Seltz call itself is a few lines. What makes the demo interesting is how many times that call runs, across how many angles, and that is really the whole story.

01.What the demo does

The demo is a single screen with one input: a company domain. You type something like stripe.com, hit run, and a minute or two later a console fills in. First a profile of the company you entered, then an inferred picture of who it sells to, then a list of target accounts scored for fit, and finally a few senior decision-makers at the best of those accounts. Every company comes with the sources it was read from, and every person with a link to their profile.

It does not arrive all at once. The run moves through five stages, and the screen streams each one as it lands: resolving the company, inferring its ideal customer, prospecting for accounts, scoring them, and finding people. Here is the thing to hold onto before we look at any code: the model never browses. It decides what to look for and judges what comes back. Everything real on that screen came out of a Seltz search.

02.The architecture: who reasons, who reaches the web

It is tempting, now that some models can search the web themselves, to hand the whole job to one of them and walk away. This demo splits it on purpose, because the two halves want different things.

The line between them is strict. The model writes queries and reads results; it never reaches the web itself. One optional scraper can pull a company’s own homepage when that is the best source, and it quietly switches off when its key is absent, but the live web, the news, and the people all arrive through Seltz. The model asks, and the server is the only thing that actually goes and looks.

03.The Seltz call

Here is the hero, the reason any of this works. When a stage needs something real from the web, this is what answers. It is short, because Seltz does the hard part.

typescript
// app/api/gtm/search/route.ts  (server)
import { Seltz } from "seltz";

const seltz = new Seltz({ apiKey: process.env.SELTZ_API_KEY });

// one tiny wrapper is the whole web-facing surface of the demo
async function seltzSearch(query: string, count: number, scope?: string) {
  const result = await seltz.search({ query, maxResults: count, scope });

  // each doc is clean, readable content with its source url attached,
  // the exact shape a model can read and judge
  return (result.documents ?? []).map((doc) => ({
    url: doc.url ?? "",
    content: doc.content ?? "",
  }));
}

The three lines that matter are the import, the client, and the call: new Seltz(...) with your server-side key, then seltz.search({ query, maxResults: count, scope }). What comes back is result.documents, an array where each entry is a url and a block of clean content. That is the part worth pausing on. It is not raw HTML for you to scrape and untangle; it is readable text with the source attached, which is exactly what a model can reason over.

The scope is the lever. Leave it off and you search the open web for companies and pages. Pass "news" and you get recent funding, hiring, and launch coverage. Pass "people" and you get senior profiles instead of web pages. Same function, same shape of result, three different kinds of truth. For the full set of parameters the call accepts, the Seltz docs cover the search API.

What is not here matters just as much. No crawler to run, no search index to keep fresh, no people-data vendor to wire up and renew. You ask Seltz for companies, news, or people, and it hands them back.

04.The same call, three scopes, fanned out

One query is never enough here. Profiling the seed company is a couple of searches. Turning its ideal customer into prospects is one search per angle, and there are several angles. Scoring a single account reads its site, its overview, and its recent news, which is three more searches, and the demo scores a dozen accounts. Finding people at the top accounts is another search each. Add it up and a single run fans out around twenty Seltz searches across three scopes.

The trick is that they nearly all run at the same time. Angles are searched in parallel, accounts are scored in parallel, and within each account the web and news lookups fire together. Here is the shape of it.

typescript
// scoring one account: read its site and its press at the same time
const [web, about, news] = await Promise.all([
  seltzSearch(`${name} ${domain}`, 4),
  seltzSearch(`${name} overview what they do customers`, 4),
  seltzSearch(`${name} funding hiring product launch`, 3, "news"),
]);

// finding contacts: the exact same call, pointed at the people scope
const people = await seltzSearch(
  `${name} ${domain} leadership executives`,
  8,
  "people",
);

// across the run, every buyer angle the model wrote is its own search
const perAngle = await Promise.all(
  angles.map((angle) => seltzSearch(angle, 6)),
);

Strip the orchestration away and the only thing reaching the outside world is still seltz.search(), now called many times across web, news, and people instead of once. The model supplies the queries and the judgment. Seltz supplies the companies, the signals, and the people.

05.Why it runs as a background job

A full run does real work: five stages, around twenty searches, and a model reading every batch of results. It is worth being precise about where that minute or two goes, though. The Seltz searches are not the slow part — each one returns in milliseconds. What takes the time is the model: profiling the company, inferring the ideal customer, and scoring a dozen accounts is a lot of reading and judging. That reasoning is far too long to hold a browser request open and wait on. So the browser does not wait. It posts the domain, gets back a run id, and then follows a stream of events: profile ready, ideal customer inferred, this account scored 82, four decision-makers found.

typescript
// app/api/gtm/search/runs/route.ts  (server)
export async function POST(request: Request) {
  const body = await request.json();
  const runId = crypto.randomUUID();
  const origin = new URL(request.url).origin;

  await createGtmRun(runId);

  // a full run fans out ~20 Seltz searches and can take a minute or two,
  // so we start it in the background and hand the browser an id to follow
  after(async () => {
    await consumeSearchRun(runId, origin, body);
  });

  return Response.json({ runId });
}

The run executes in the background, and the page remembers what it has already shown, so a reload in the middle brings the results so far right back instead of a blank screen. From where you sit it just looks like watching the agent work through the list out loud. What is actually being streamed underneath is the progress of a pile of Seltz searches.

06.Where the keys live

Step back and look at where the secrets sit, because for anything you would put in front of real users this is the part that matters most. A few keys are in play: the Seltz key, the model provider key, and the optional scraper key. Every one of them stays on the server, read from environment variables, and none is ever handed to the browser.

The browser only ever talks to your own /api/gtm routes. Those routes hold the secrets and make the real calls. The one capability that reaches the live web sits behind your endpoint, where you can rate-limit it, log it, and decide what the model is allowed to act on. The model asks; the server decides whether and how to honor it.

07.Conclusion

The satisfying part to build is the pipeline: a domain becomes a profile, a profile becomes an ideal customer, that becomes a set of angles, the angles become scored accounts, and the accounts become people. That is the part you get to be clever about. But none of it is worth anything if the companies and the people are not real. What makes them real is one call, seltz.search({ query, maxResults, scope }), run across as many angles and scopes as the report needs.

That is the takeaway worth keeping. A single Seltz call is enough to ground one answer, and the same call scales: point it at the open web, at news, and at people, fan it out across a whole run, and it turns one company domain into a cited GTM report. You can try the live demo at demo.seltz.ai/gtm-intelligence, and when you want to build your own, grab a key from the Seltz console and point a search call at whatever you need to find.