@fiberai/sdk
v0.0.14
Published
Official TypeScript SDK for Fiber AI API
Readme
@fiberai/sdk
Official TypeScript/JavaScript SDK for the Fiber AI API - Reach anyone on the planet with verified contacts
For AI agents (Cursor / Claude / Codex / ChatGPT / Copilot)
Do NOT guess operation names — read the canonical agent-facing docs first:
- Routing index + critical rules: https://api.fiber.ai/llms.txt
- Per-operation markdown:
https://api.fiber.ai/ai-docs/<operationId>.md(e.g.companySearch,syncQuickContactReveal,triggerExhaustiveContactEnrichment) - Operation index: https://api.fiber.ai/ai-docs/index.md
- Full corpus (single file): https://api.fiber.ai/llms-full.txt
- OpenAPI spec:
https://api.fiber.ai/openapi.json(sendAccept: text/markdownfor the agent-friendly variant) - MCP server:
https://mcp.fiber.ai/mcp/v2(also see fiber-ai/mcp)
Table of Contents
- Installation
- Quick Start
- Authentication
- Core Features
- Advanced Usage
- Error Handling
- TypeScript Support
- Rate Limits & Credits
- Support
Installation
npm install @fiberai/sdk
# or
yarn add @fiberai/sdk
# or
pnpm add @fiberai/sdkRequirements: Node.js 18.0.0 or higher
Quick Start
import { peopleSearch, companySearch, getOrgCredits } from "@fiberai/sdk";
// Check your organization's credit balance
const credits = await getOrgCredits({
query: { apiKey: "your-api-key" },
});
console.log(`Available credits: ${credits.data?.output.available}`);
// Search for companies
const companies = await companySearch({
body: {
apiKey: "your-api-key",
searchParams: {
industriesV2: {
anyOf: ["Software", "Information Technology"],
},
// Bucket-valued range. Allowed bounds: 0 | 1 | 10 | 50 | 200 | 500 | 1000 | 5000 | 10000 | null.
employeeCountV2: {
lowerBoundExclusive: 50,
upperBoundInclusive: 1000,
},
headquartersCountryCode: {
anyOf: ["USA"],
},
},
pageSize: 25,
},
});
// Search for people
const people = await peopleSearch({
body: {
apiKey: "your-api-key",
searchParams: {
jobTitleV2: {
anyOf: [
{ type: "term", term: "CEO" },
{ type: "term", term: "CTO" },
{ type: "static-groups", groups: ["c-suite"] },
],
},
country3LetterCode: { anyOf: ["USA"] },
},
pageSize: 25,
},
});
// Every successful response also carries a `chargeInfo` envelope alongside
// `output`. Source of truth for cost — prefer this over the static table below.
console.log(people.data?.chargeInfo);
// e.g. { method: 'charged-now', creditsCharged: 25, lowCreditAlert: null }Authentication
All API requests require an API key. Get yours at fiber.ai/app/api.
Include your API key in every request:
- POST requests: Pass
apiKeyin the request body - GET requests: Pass
apiKeyas a query parameter
// POST request example
await companySearch({
body: {
apiKey: process.env.FIBERAI_API_KEY,
searchParams: {
/* ... */
},
},
});
// GET request example
await getOrgCredits({
query: { apiKey: process.env.FIBERAI_API_KEY },
});Best Practice: Store your API key in environment variables:
# .env
FIBERAI_API_KEY=your_api_key_hereCore Features
Company & People Search
Company Search
Search for companies using 40+ filters including industry, location, revenue, funding, and more.
import { companySearch, companyCount } from "@fiberai/sdk";
// Advanced company search
const result = await companySearch({
body: {
apiKey: process.env.FIBERAI_API_KEY,
searchParams: {
// Industry filters
industriesV2: {
anyOf: ["Software", "Cloud"],
},
// Size filters
employeeCountV2: {
lowerBoundExclusive: 50,
upperBoundInclusive: 500,
},
// Location filters
headquartersCountryCode: {
anyOf: ["USA"],
},
// Funding filters
totalFundingUSD: {
lowerBound: 1000000,
},
// Keywords filter
keywords: {
containsAny: ["venture-backed-startup"],
},
},
pageSize: 50,
cursor: null, // For pagination
},
});
console.log(`Found ${result.data?.output.data.length} companies`);
console.log(
`Cursor for next page: ${result.data?.output.nextCursor ?? "none"}`,
);
// Get total count before searching
const count = await companyCount({
body: {
apiKey: process.env.FIBERAI_API_KEY,
searchParams: {
/* same filters */
},
},
});
console.log(`Total companies matching: ${count.data?.output.count}`);People Search
Find decision-makers and key contacts with precise targeting.
import { peopleSearch, peopleSearchCount } from "@fiberai/sdk";
const people = await peopleSearch({
body: {
apiKey: process.env.FIBERAI_API_KEY!,
searchParams: {
// Job title filter. Mix free-form `term` matches with curated
// `static-groups` ('founder' | 'c-suite' | 'board-member') and
// `dynamic-groups` ('vp' | 'director' | 'management' | 'entry-level' | ...).
jobTitleV2: {
anyOf: [
{ type: "term", term: "CEO" },
{ type: "term", term: "Chief Executive Officer" },
{ type: "static-groups", groups: ["founder", "c-suite"] },
],
},
// Country filter — ISO 3166-1 alpha-3 codes, not city names.
country3LetterCode: { anyOf: ["USA"] },
// Geographic radius filter (use this, not "cities"/"countries").
location: {
unionAll: [
{
strategy: "radial-distance",
center: { latitude: 37.7749, longitude: -122.4194 }, // San Francisco
radius: { unit: "miles", quantity: 50 },
},
],
},
// Curated tags. Allowed values include 'decision-maker', 'c-suite',
// 'experienced-executive', 'second-time-founder', 'phd', etc.
tags: { anyOf: ["decision-maker", "c-suite"] },
// Education filter — match by school identifier (LinkedIn id, slug,
// or domain), not by free-form school name.
education: {
anyOf: [
{
school: {
anyOf: [{ domain: "stanford.edu" }, { domain: "harvard.edu" }],
},
},
],
},
getDetailedWorkExperience: true,
getDetailedEducation: true,
},
// `currentCompanies` is a TOP-LEVEL body field, NOT inside searchParams.
// It expects concrete identifiers, not industry filters. To filter by
// industry/size/etc, use `syncCombinedSearch` and put company filters
// under `companyParams`.
currentCompanies: [
{ domain: "example.com" },
{ linkedinSlugOrURL: "https://linkedin.com/company/example" },
],
pageSize: 100,
},
});
// Access profile data — `output.data` is a direct array, no `.items` wrapper.
people.data?.output.data.forEach((profile) => {
console.log(`${profile.name} — ${profile.headline}`);
console.log(`LinkedIn: ${profile.url}`);
// There is no `profile.currentJob` field. Current role lives in
// `experiences[]` filtered by `is_current`.
const currentRole = profile.experiences?.find((e) => e.is_current);
if (currentRole) {
console.log(`Current: ${currentRole.title} at ${currentRole.company_name}`);
}
});
console.log(`Charged: ${people.data?.chargeInfo.method}`);Combined Search (Companies + People)
Return matching companies and their employees in a single synchronous call. Filter on the company side (companyParams) and the person side (profileParams) at the same time. For larger result sets you can also run companySearch and peopleSearch independently and paginate each.
import { syncCombinedSearch } from "@fiberai/sdk";
const result = await syncCombinedSearch({
body: {
apiKey: process.env.FIBERAI_API_KEY!,
companyParams: {
industriesV2: {
anyOf: ["Software"],
},
employeeCountV2: {
lowerBoundExclusive: 200,
},
},
profileParams: {
jobTitleV2: {
anyOf: [
{ type: "term", term: "VP of Sales" },
{ type: "term", term: "Sales Director" },
{ type: "static-groups", groups: ["c-suite"] },
{ type: "dynamic-groups", groups: ["vp", "director"] },
],
},
},
companyItemLimit: 25,
profileItemLimit: 100,
},
});
result.data?.output.companies?.forEach((company) => {
console.log(company.preferred_name);
});
result.data?.output.profiles?.forEach((profile) => {
console.log(`${profile.name} - ${profile.headline}`);
});See
syncCombinedSearchfor the full parameter surface, or drive the two-step flow viacompanySearch+peopleSearchwhen you need cursor-based pagination.
Contact Enrichment
Reveal emails and phone numbers for a known LinkedIn profile. Fiber exposes a three-tier public contract — pick the tier that matches your latency, coverage, and cost tradeoffs:
| Tier | Operation | Shape | When to use |
| ---------- | ------------------------------------------------------------------------------ | --------------------- | ------------------------------------------------------------------------------------------------------------- |
| Default | syncQuickContactReveal | Sync, one HTTP call | Default single-profile reveal. Fast, good coverage. |
| Premium | syncTurboContactEnrichment | Sync, one HTTP call | You need the absolute fastest response and want the widest first-pass waterfall. Premium cost. |
| Exhaustive | triggerExhaustiveContactEnrichment + pollExhaustiveContactEnrichmentResult | Async, trigger + poll | Highest coverage. Kick off a background waterfall that tries every available provider, then poll for results. |
For lists of 10–2000 identifiers, use the batch endpoints instead (see Batch Contact Reveal).
Default — syncQuickContactReveal
import { syncQuickContactReveal } from "@fiberai/sdk";
const result = await syncQuickContactReveal({
body: {
apiKey: process.env.FIBERAI_API_KEY!,
linkedinUrl: "https://www.linkedin.com/in/example",
enrichmentType: {
getWorkEmails: true,
getPersonalEmails: true,
getPhoneNumbers: true,
},
// Emails are bounce-validated by default. Set validateEmails: false to skip.
},
});
console.log("Emails:", result.data?.output.profile?.emails);
console.log("Phones:", result.data?.output.profile?.phoneNumbers);Premium — syncTurboContactEnrichment
import { syncTurboContactEnrichment } from "@fiberai/sdk";
const result = await syncTurboContactEnrichment({
body: {
apiKey: process.env.FIBERAI_API_KEY!,
linkedinUrl: "https://www.linkedin.com/in/example",
enrichmentType: {
getWorkEmails: true,
getPersonalEmails: true,
getPhoneNumbers: true,
},
},
});
console.log("Emails:", result.data?.output.profile?.emails);Exhaustive (async waterfall) — triggerExhaustiveContactEnrichment + pollExhaustiveContactEnrichmentResult
triggerExhaustiveContactEnrichment starts a background waterfall that returns the highest coverage at the cost of latency. It returns a taskId immediately; poll pollExhaustiveContactEnrichmentResult every 5–15s until output.done === true.
import {
triggerExhaustiveContactEnrichment,
pollExhaustiveContactEnrichmentResult,
} from "@fiberai/sdk";
const trigger: Awaited<ReturnType<typeof triggerExhaustiveContactEnrichment>> =
await triggerExhaustiveContactEnrichment({
body: {
apiKey: process.env.FIBERAI_API_KEY!,
linkedinUrl: "https://www.linkedin.com/in/example",
enrichmentType: {
getWorkEmails: true,
getPersonalEmails: true,
getPhoneNumbers: true,
},
},
});
const taskId: string = trigger.data!.output.taskId;
let done: boolean = false;
while (!done) {
await new Promise<void>((resolve) => setTimeout(resolve, 5000));
const poll: Awaited<ReturnType<typeof pollExhaustiveContactEnrichmentResult>> =
await pollExhaustiveContactEnrichmentResult({
body: { apiKey: process.env.FIBERAI_API_KEY!, taskId },
});
done = poll.data?.output.done ?? false;
if (done) {
console.log("Emails:", poll.data?.output.profile.emails);
console.log("Phones:", poll.data?.output.profile.phoneNumbers);
console.log("Status:", poll.data?.output.profile.status);
}
}Batch Contact Reveal — startBatchContactEnrichment + pollBatchContactEnrichment
For 10–2000 LinkedIn identifiers in one task. startBatchContactEnrichment charges credits up front and returns a taskId; pollBatchContactEnrichment returns paginated results with both per-task progress (output.overallStats) and output.done to gate the loop. For larger lists or repeatable workflows, upload a CSV-backed audience and enrich it through the audience workflow instead.
import {
startBatchContactEnrichment,
pollBatchContactEnrichment,
} from "@fiberai/sdk";
const start: Awaited<ReturnType<typeof startBatchContactEnrichment>> =
await startBatchContactEnrichment({
body: {
apiKey: process.env.FIBERAI_API_KEY!,
personDetails: [
{ linkedinUrl: { value: "https://www.linkedin.com/in/example1" } },
{ linkedinUrl: { value: "https://www.linkedin.com/in/example2" } },
],
enrichmentTypes: {
getWorkEmails: true,
getPersonalEmails: true,
getPhoneNumbers: true,
},
},
});
const taskId: string = start.data!.output.taskId;
console.log(`Queued ${start.data!.output.numPeopleEnqueued} profiles`);
let cursor: string | null | undefined = undefined;
let done: boolean = false;
while (!done) {
await new Promise<void>((resolve) => setTimeout(resolve, 10000));
const poll: Awaited<ReturnType<typeof pollBatchContactEnrichment>> =
await pollBatchContactEnrichment({
body: {
apiKey: process.env.FIBERAI_API_KEY!,
taskId,
cursor,
take: 100,
},
});
if (!poll.data) break;
for (const row of poll.data.output.pageResults) {
console.log(row.inputs.linkedinUrl.value, row.outputs?.emails);
}
done = poll.data.output.done;
cursor = poll.data.output.nextCursor ?? null;
// `nextCursor` may go null before `done` flips — pause and re-poll if so.
if (!done && !cursor) await new Promise<void>((r) => setTimeout(r, 5000));
}LinkedIn Live Enrichment
Get real-time data from LinkedIn with live scraping.
Live Profile Enrichment
import { profileLiveEnrich } from "@fiberai/sdk";
const profile = await profileLiveEnrich({
body: {
apiKey: process.env.FIBERAI_API_KEY!,
// `identifier` accepts a slug ('williamhgates'), a full LinkedIn URL,
// a Sales Navigator URN ('ACwAAA...'), or a numeric LinkedIn user ID.
identifier: "https://www.linkedin.com/in/example",
},
});
// `output` is a discriminated union: { found: true; profile: ... } |
// { found: false; message: string }. Narrow on `output.found` before
// touching `output.profile`.
const out = profile.data?.output;
if (out?.found) {
console.log(out.profile.name);
console.log(out.profile.summary);
console.log(out.profile.experiences);
console.log(out.profile.detailed_education);
} else if (out) {
console.warn("not found:", out.message);
}Live Company Enrichment
import { companyLiveEnrich } from "@fiberai/sdk";
const company = await companyLiveEnrich({
body: {
apiKey: process.env.FIBERAI_API_KEY!,
// The `type` discriminator picks how `value` is interpreted:
// 'slug' — LinkedIn slug, e.g. 'microsoft'
// 'orgId' — LinkedIn organization id, e.g. '1441'
// 'liUrl' — full LinkedIn URL, e.g. 'https://www.linkedin.com/company/microsoft'
type: "liUrl",
value: "https://www.linkedin.com/company/example",
},
});
// Company fields live under `output.company`, not directly on `output`.
console.log(company.data?.output.company.headline);
console.log(company.data?.output.company.description);
console.log(company.data?.output.company.follower_count);Fetch LinkedIn Posts
import { profilePostsLiveFetch, companyPostsLiveFetch } from "@fiberai/sdk";
// Get profile posts. `identifier` accepts a slug, full LinkedIn URL,
// or Sales Navigator URN — same shape as `profileLiveEnrich`.
const profilePosts = await profilePostsLiveFetch({
body: {
apiKey: process.env.FIBERAI_API_KEY!,
identifier: "https://www.linkedin.com/in/example",
cursor: null, // For pagination
},
});
// Get company posts. `identifier` accepts a LinkedIn slug, URL, or org ID.
const companyPosts = await companyPostsLiveFetch({
body: {
apiKey: process.env.FIBERAI_API_KEY!,
identifier: "https://www.linkedin.com/company/example",
cursor: null,
},
});Exclusion Lists
Manage exclusion lists to filter out companies or prospects from searches.
import {
createCompanyExclusionList,
addCompaniesToExclusionList,
getExcludedCompaniesForExclusionList,
createCompanyExclusionListFromAudience,
} from "@fiberai/sdk";
// Create an exclusion list
const list = await createCompanyExclusionList({
body: {
apiKey: process.env.FIBERAI_API_KEY,
name: "Competitors",
isOrganizationWide: true,
},
});
// Add companies to the list
await addCompaniesToExclusionList({
body: {
apiKey: process.env.FIBERAI_API_KEY,
listId: list.data.output.listId,
companies: [
{ domain: "competitor1.com", linkedinUrl: null },
{ domain: "competitor2.com", linkedinUrl: null },
],
},
});
// Create exclusion list from an existing audience
const audienceList = await createCompanyExclusionListFromAudience({
body: {
apiKey: process.env.FIBERAI_API_KEY,
audienceId: "audience-123",
name: "Existing Customers",
isOrganizationWide: true,
},
});
// View excluded companies
const excluded = await getExcludedCompaniesForExclusionList({
body: {
apiKey: process.env.FIBERAI_API_KEY,
exclusionListId: list.data.output.listId,
pageSize: 100,
},
});Google Maps Search
Search for local businesses on Google Maps.
import {
googleMapsSearch,
checkGoogleMapsResults,
pollGoogleMapsResults,
} from "@fiberai/sdk";
// Start Google Maps search. `query` is the keywords only (no location info)
// and `strategy` is required — see below for the available strategies.
const search = await googleMapsSearch({
body: {
apiKey: process.env.FIBERAI_API_KEY!,
query: "coffee shops",
maxResults: 100,
strategy: {
// Options:
// { strategy: 'whole-usa' }
// { strategy: 'specific-areas', unionAll: [{ regionType: 'circle', center: {latitude, longitude}, radiusMiles }] }
// { strategy: 'world-cities', countriesAndRegions: { unionAll: ['USA', 'GBR', ...] } }
strategy: "specific-areas",
unionAll: [
{
regionType: "circle",
center: { latitude: 37.7749, longitude: -122.4194 },
radiusMiles: 10,
},
],
},
},
});
const searchID = search.data!.output.searchID;
// Check search progress.
const progress = await checkGoogleMapsResults({
body: { apiKey: process.env.FIBERAI_API_KEY!, searchID },
});
console.log(`Progress: ${progress.data?.output.percentageCompleted}%`);
// Poll for results once complete.
if (progress.data?.output.status === "COMPLETED") {
const results = await pollGoogleMapsResults({
body: {
apiKey: process.env.FIBERAI_API_KEY!,
searchID,
pageSize: 50,
},
});
results.data?.output.results.forEach((place) => {
console.log(`${place.name} — ${place.address}`);
console.log(`Rating: ${place.rating}, Reviews: ${place.numReviews}`);
console.log(`Website: ${place.website}`);
});
}AI-Powered Research
Use AI agents for intelligent company research and domain lookup.
import { domainLookupTrigger, domainLookupPolling } from "@fiberai/sdk";
// Trigger domain lookup. `overAllContext` is required and helps the agent
// disambiguate similar names; `companyInfo` is an array of objects (not
// a list of bare strings).
const lookup = await domainLookupTrigger({
body: {
apiKey: process.env.FIBERAI_API_KEY!,
overAllContext: "YC startups in the US",
companyInfo: [
{ name: "Acme Corp" },
{ name: "Globex", country: "USA" },
{ name: "Initech" },
],
},
});
const domainAgentRunId = lookup.data!.output.domainAgentRunId;
// Poll for results.
let lookupDone = false;
while (!lookupDone) {
await new Promise((resolve) => setTimeout(resolve, 3000));
const results = await domainLookupPolling({
body: {
apiKey: process.env.FIBERAI_API_KEY!,
domainAgentRunId,
pageSize: 10,
},
});
lookupDone = results.data?.output.status === "DONE";
if (lookupDone) {
results.data?.output.data.forEach((company) => {
console.log(`${company.companyName}: ${company.bestDomain}`);
console.log(`Confidence: ${company.confidence}/10`);
console.log(`Rationale: ${company.rationale}`);
});
}
}Advanced Usage
Configuring the built-in client
The SDK exports a pre-configured client that already points at https://api.fiber.ai. Every operation uses it by default — you do not need to call createClient to make requests.
If you need to customize headers, swap in a custom fetch, or add request/response interceptors, mutate the shared instance:
import { client } from "@fiberai/sdk";
// One-time configuration: extra headers, a custom fetch, etc.
client.setConfig({
headers: { "X-Trace-Id": "my-app/1.0.0" },
});
// Log every outgoing request — handy as a poor man's debug mode.
client.interceptors.request.use((request) => {
console.log(`[fiberai] ${request.method} ${request.url}`);
return request;
});createClient from the package is for the rare case where you want a second, independent client (e.g. multi-tenant apps). Pass it via the client: option on any operation.
Pagination Helper
import type { CompanySearchData, CompanySearchResponse } from "@fiberai/sdk";
async function* paginateCompanies(
initial: CompanySearchData,
): AsyncGenerator<
NonNullable<CompanySearchResponse["data"]>["output"]["data"]
> {
let cursor: string | null | undefined = initial.body.cursor ?? null;
do {
const result = await companySearch({
...initial,
body: { ...initial.body, cursor },
});
if (!result.data) break;
yield result.data.output.data;
cursor = result.data.output.nextCursor;
} while (cursor);
}
// Usage
for await (const companies of paginateCompanies({
body: {
apiKey: process.env.FIBERAI_API_KEY!,
searchParams: {
/* ... */
},
pageSize: 100,
},
})) {
console.log(`Processing batch of ${companies.length} companies`);
// Process batch
}Utility Endpoints
import {
getRegions,
getLanguages,
getTimeZones,
getIndustries,
getTags,
getNaicsCodes,
getAccelerators,
} from "@fiberai/sdk";
// Get available regions for filtering
const regions = await getRegions({
query: { apiKey: process.env.FIBERAI_API_KEY },
});
// Get available industries
const industries = await getIndustries({
query: { apiKey: process.env.FIBERAI_API_KEY },
});
// Get profile and company tags
const tags = await getTags({
query: { apiKey: process.env.FIBERAI_API_KEY },
});Error Handling
Standard Error Handling
By default, every operation returns { data, error, response }. Branch on the HTTP status from response.status rather than string-matching the error body — error payloads are not guaranteed to contain a parseable message for every failure mode.
import { companySearch } from "@fiberai/sdk";
const result = await companySearch({
body: {
apiKey: process.env.FIBERAI_API_KEY!,
searchParams: {
/* ... */
},
},
});
if (result.error) {
switch (result.response.status) {
case 400:
console.error("Bad request:", result.error);
break;
case 401:
console.error("Invalid API key");
break;
case 402:
// 402 errors carry an `outOfCreditsAlert` with a top-up link.
console.error("Insufficient credits:", result.error);
break;
case 429:
console.error("Rate limit exceeded — back off and retry");
break;
default:
console.error(
`Request failed (${result.response.status}):`,
result.error,
);
}
} else {
console.log("Success:", result.data?.output);
console.log("Charged:", result.data?.chargeInfo);
}Throwing on Errors
try {
const result = await companySearch({
body: {
apiKey: process.env.FIBERAI_API_KEY!,
searchParams: {
/* ... */
},
},
throwOnError: true,
});
// result.data is guaranteed to exist when throwOnError is true.
console.log(result.data.output);
} catch (error) {
console.error("Request failed:", error);
}Common HTTP Error Codes
| Code | Meaning | Solution | | ---- | --------------------- | ------------------------------------------ | | 400 | Bad Request | Check your request parameters | | 401 | Unauthorized | Verify your API key is valid | | 402 | Payment Required | Insufficient credits - top up your account | | 403 | Forbidden | You don't have access to this resource | | 404 | Not Found | Resource doesn't exist | | 429 | Too Many Requests | Rate limit exceeded - slow down requests | | 500 | Internal Server Error | Contact support |
TypeScript Support
All SDK methods are fully typed with TypeScript.
import { companySearch } from "@fiberai/sdk";
// SDK functions return `{ data, error, response }`. Annotate the awaited call
// with `Awaited<ReturnType<typeof fn>>` — the exported `<Op>Response` /
// `<Op>Errors` types describe the inner body union, NOT the wrapper.
const result: Awaited<ReturnType<typeof companySearch>> = await companySearch({
body: {
apiKey: process.env.FIBERAI_API_KEY!,
searchParams: {
industriesV2: { anyOf: ["Software"] },
employeeCountV2: {
lowerBoundExclusive: 50,
upperBoundInclusive: 1000,
},
},
pageSize: 25,
},
});
if (result.data) {
console.log(`${result.data.output.data.length} companies`);
console.log(result.data.chargeInfo);
}
// Need a name for the request shape (e.g. building a request in one place,
// passing it elsewhere)? Derive it from the function — the bare `<Op>Data`
// export carries an internal `url` literal and is not meant to be
// hand-constructed.
type CompanySearchArgs = Parameters<typeof companySearch>[0];Naming convention: every operation
fooships companion types:FooResponse(200 body union) andFooErrors(4xx/5xx body union). UseParameters<typeof foo>[0]for input args andAwaited<ReturnType<typeof foo>>for the result envelope. The same applies topeopleSearch,syncQuickContactReveal, etc.
Runtime Validation with Zod
Every request and response shape is also published as a Zod schema, generated from the same OpenAPI spec as the TypeScript types. They live behind the dedicated @fiberai/sdk/zod subpath so apps that don't need runtime validation pay zero bundle cost — the main entry stays under 50 KB while the Zod bundle (~3–6 MB depending on the API surface) is only loaded if you import it.
import { zPeopleSearchData, zCompanySearchData } from "@fiberai/sdk/zod";
import { peopleSearch } from "@fiberai/sdk";
const rawInput: unknown = JSON.parse(req.body);
const parsed: ReturnType<typeof zPeopleSearchData.parse> =
zPeopleSearchData.parse(rawInput);
const result: Awaited<ReturnType<typeof peopleSearch>> =
await peopleSearch(parsed);Use safeParse if you want to handle validation failures without throwing. Zod 4 ships top-level error helpers — use z.flattenError for form-friendly output or z.treeifyError for nested shapes:
import { z } from "zod";
import { zCompanySearchData } from "@fiberai/sdk/zod";
const parsed: ReturnType<typeof zCompanySearchData.safeParse> =
zCompanySearchData.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({ errors: z.flattenError(parsed.error) });
}Naming convention: every TypeScript type
Foohas a matchingzFooschema. SoPeopleSearchData↔zPeopleSearchData,CompanySearchResponse↔zCompanySearchResponse, etc.
zod ships as a runtime dependency of @fiberai/sdk and is externalized from the bundle (so package managers dedupe with whatever Zod copy your app already has). If you don't import @fiberai/sdk/zod, your bundler tree-shakes the schemas out and you never pay for them.
Rate Limits & Credits
Rate Limits
Each endpoint has its own rate limit. Common limits:
- Company/People Search: 180 requests/minute
- Contact Enrichment: 120 requests/minute (single), 10 requests/minute (batch)
- Live Enrichment: 60 requests/minute
- Utility Endpoints: 10-50 requests/minute
Credit Costs
Pricing varies by plan and is configured per-organization. Don't hard-code costs into your app — every successful response carries a chargeInfo envelope alongside output that tells you exactly what was charged for that call:
const result = await peopleSearch({
/* ... */
});
console.log(result.data?.chargeInfo);
// {
// method: 'charged-now', // 'charged-now' | 'charging-later' | 'charged-for-async-process' | 'free'
// creditsCharged: 25, // present when method !== 'free'
// lowCreditAlert: null // populated with a top-up URL when running low
// }For a non-charging dry-run estimate of contact enrichment costs, use estimateEnrichmentCost. For the per-org pricing breakdown across operations, inspect getOrgCredits().data.output.creditsPerOperation.
Free Endpoints
The following endpoints never charge credits:
getOrgCredits— Check credit balancegetRegions,getLanguages,getTimeZones,getIndustries,getTags,getNaicsCodes,getAccelerators— Utility endpoints- All exclusion-list management endpoints
Check Your Credits
import { getOrgCredits } from "@fiberai/sdk";
const credits = await getOrgCredits({
query: { apiKey: process.env.FIBERAI_API_KEY! },
});
console.log(`Available: ${credits.data?.output.available}`);
console.log(`Used: ${credits.data?.output.used}`);
console.log(`Max: ${credits.data?.output.max}`);
console.log(`Resets on: ${credits.data?.output.usagePeriodResetsOn}`);Support
- Documentation: api.fiber.ai/docs
- Email: [email protected]
- Website: fiber.ai
- Get API Key: fiber.ai/app/api
License
MIT
Made with ❤️ by Fiber AI
