TypeScript Client
Strongly-typed TypeScript SDK for running enrichment pipes and provider searches against the pipe0 API.
The @pipe0/client package is a fully typed TypeScript SDK for the pipe0
API. It turns pipe and search IDs into autocomplete-friendly,
compile-checked entrypoints, so you get real feedback from your editor the
moment a pipe expects different config than you've written.
Installation
pnpm add @pipe0/clientCreating a client
import { Pipe0 } from "@pipe0/client";
export const pipe0 = new Pipe0({
apiKey: process.env.PIPE0_API_KEY,
});All options:
| Option | Default | Notes |
|---|---|---|
apiKey | process.env.PIPE0_API_KEY | Sent as Authorization: Bearer …. |
baseUrl | https://api.pipe0.com | Override for self-hosted / staging. |
Data Enrichment (pipes)
Use pipes.pipe() to enrich up to 100 input objects at a time. Use pipes.pipeInBatches() to enrich
any number of input objects.
Examples Split a name
const result = await pipe0.pipes.pipe({
config: { environment: "sandbox" },
pipes: [{ pipe_id: "people:name:split@1" }],
input: [{ id: "1", name: "John Doe" }],
});
const record = result.records["1"]; // "1" is the id property
record.fields.first_name.value; // "John"
record.fields.last_name.value; // "Doe"Chain pipes (enrich → LLM → send)
const result = await pipe0.pipes.pipe({
config: { environment: "sandbox" },
pipes: [
{ pipe_id: "people:profile:waterfall@1" },
{
pipe_id: "prompt:run@1",
config: {
model: "gemini-flash-latest",
prompt: {
template: `
Profile: {{ input profile type="json" }}
{{ output icp_fit type="boolean" description="Does this match our ICP?" }}
{{ output reasoning type="string" description="Why or why not?" }}
`,
},
},
},
{
pipe_id: "message:send:slack@1",
trigger: { action: "run", when: { icp_fit: { equals: true } } },
config: { channel: "#new-leads" },
},
],
input: [{ id: "1", email: "jane@acme.com" }],
});Note trigger — the Slack pipe only runs when the previous step marked
icp_fit true. Conditional execution is part of the typed payload.
Large inputs: pipeInBatches
Splits input into chunks and runs them with bounded concurrency.
import { Pipe0BatchError } from "@pipe0/client";
try {
const batches = await pipe0.pipes.pipeInBatches(
{
pipes: [{ pipe_id: "people:workemail:waterfall@1" }],
input: tenThousandRows,
},
{
stopOnError: false,
onBatchComplete: (i, res) => console.log(`batch ${i}: ${res.status}`),
},
);
} catch (err) {
if (err instanceof Pipe0BatchError) {
console.error(`${err.errors.length} batches failed`);
console.log(`${err.successfulBatches.length} succeeded`);
}
}Advanced: Manual polling
If you want control over the the polling process, use the manual handlers.
// Sends a valid API requests
const runId = await pipe0.pipes.create({
pipes: [{ pipe_id: "people:workemail:waterfall@1" }],
input: largeInput,
});
// Check the status of the task
const status = await pipe0.pipes.check(runId);
// Or manually wait until complete (same as pipes.pipe())
const done = await pipe0.pipes.waitUntilComplete(runId, {
onPoll: (response) => console.log(response.status),
});Running searches
Searches discover new records from external providers (Crustdata, Exa, Icypeas, Clado, Leadmagic, …) based on filters.
Find people by title and location
const result = await pipe0.searches.search({
config: {
environment: "sandbox",
},
searches: [
{
search_id: "people:profiles:crustdata@1",
config: {
limit: 25,
filters: {
current_job_titles: { include: ["Head of RevOps"] },
locations: { include: ["San Francisco", "New York"] },
},
},
},
],
});
for (const row of result.results) {
row.name.value; // string
row.job_title.value; // string
row.company_website_url; // typed per output_fields
}Combining searches with pipes
The output of a search is a set of records — feed them into a pipe to enrich further:
const input = await pipe0.searches.search({ /* … */ });
const enriched = await pipe0.pipes.pipe({
config: { environment: "sandbox" },
pipes: [{ pipe_id: "people:workemail:waterfall@1" }],
input,
});Error handling
The client throws typed errors you can discriminate on:
import {
Pipe0TimeoutError,
Pipe0AbortError,
Pipe0TaskError,
Pipe0ServerError,
Pipe0BatchError,
} from "@pipe0/client";
try {
await pipe0.pipes.pipe({ /* … */ });
} catch (err) {
if (err instanceof Pipe0TimeoutError) {
// Polling exceeded pollingTimeoutMs — err.runId is still valid,
// call pipes.check(err.runId) later.
} else if (err instanceof Pipe0TaskError) {
// The API returned a task-level error.
console.error(err.responseBody);
} else if (err instanceof Pipe0ServerError) {
// Non-2xx response.
}
}Cancellation uses standard AbortController:
const controller = new AbortController();
setTimeout(() => controller.abort(), 30_000);
await pipe0.pipes.pipe(payload, { signal: controller.signal });See also
- Pipe catalog — every
pipe_idand its config shape. - Search catalog — every
search_id.