@anisprouts/dsp-sdk-trial
v0.2.0
Published
Trial JavaScript/TypeScript SDK for the Sprouts Data Platform (DSP) API: search, look-alike (similar), entity lookups, and key usage.
Maintainers
Readme
@anisprouts/dsp-sdk-trial
Official JavaScript / TypeScript SDK for the Sprouts Data Platform (DSP) API.
Covers the API-key surface:
| Method | Endpoint | Scope |
|---|---|---|
| client.search(params) | POST /v1/search | dsp.search.read |
| client.similar(params) | POST /v1/similar | dsp.search.read |
| client.entities.*(params) | POST /v1/entities/{category} | dsp.entities.read |
| client.usage(params?) | GET /v1/keys/usage | dsp.usage.read |
Zero runtime dependencies. Node.js ≥ 18 (uses the built-in fetch). Ships ESM
and CJS with full TypeScript types.
📘 New to the platform? Start with the User Guide — the full journey from getting an API key to production troubleshooting. This README is the quick reference.
Install
npm install @anisprouts/dsp-sdk-trialQuick start
import { DspClient } from '@anisprouts/dsp-sdk-trial';
const client = new DspClient({
apiKey: process.env.DSP_API_KEY, // sk_dsp_live_… or sk_dsp_test_…
});
const page = await client.search({
query: 'industrial robotics companies in Germany',
mode: 'auto',
category: 'company',
limit: 25,
});
for (const hit of page.results) {
console.log(hit.id, hit.score, hit['name']);
}apiKey defaults to the DSP_API_KEY environment variable, and baseUrl to
DSP_BASE_URL (falling back to the QA environment,
https://dsp.gtmsprouts.ai/backend).
Server-side only. DSP keys carry your tenant's whole credit balance, and the API refuses any browser request carrying a key (
403 auth.browser_origin). The client throws if constructed in a browser; call it from your server and have your frontend call you.
Look-alike search
const lookalikes = await client.similar({
anchors: [
{ value: 'acme.com' }, // by: 'domain' is the default
{ value: 'Globex', by: 'name', weight: 0.5 },
],
combine: 'and', // 'and' = like all anchors together; 'or' = like any one
limit: 50,
});Entity lookup
Fetch full records by id — one method per category: companies, persons,
patents, news, trends, marketReports, fundings, academicReports,
jobOpenings. Responses are fully typed (CompanyRecord, PatentRecord, …)
and billed per requested id.
const page = await client.entities.companies({
ids: ['000006dddc0f7a39', 'definitely_not_real'],
fields: ['account_name', 'industry', 'employee_count'], // omit for the full record
});
for (const hit of page.results) {
if (!hit.found) continue; // misses are returned, never silently dropped
console.log(hit.fields.account_name, hit.fields.employee_count);
}companies and persons can also resolve raw identifiers instead of
platform ids — set by (≤ 50 ids per call in that mode):
const byDomain = await client.entities.companies({
ids: ['adyen.com', 'checkout.com'],
by: 'domain', // 'auto' | 'domain' | 'li_url' | 'name' ('domain' not valid for persons)
});Batch limits: 1000 ids per call for companies/persons, 100 for the document
categories (document records are large), 50 whenever by is set.
Usage (the calling key's spend)
const usage = await client.usage({
granularity: 'day', // 'hour' | 'day' | 'month' | 'total'
from: new Date('2026-07-01'), // Date or ISO string
to: new Date('2026-08-01'),
});
console.log(usage.total_requests, usage.total_credits, usage.buckets);Defaults to the last 30 days, bucketed by day. total_credits is net of
refunds. Only the calling key's usage is readable.
Pagination
Results are cursor-paginated (next_cursor is opaque and signed; there is no
page/offset parameter). Either follow it yourself:
let cursor: string | undefined;
do {
const page = await client.search({ query: 'fintech', cursor });
process(page.results);
cursor = page.next_cursor ?? undefined;
} while (cursor);…or use the async iterators:
for await (const page of client.searchPages({ query: 'fintech', limit: 100 })) {
process(page.results);
}
// client.similarPages(...) works the same way.Do not change filters mid-pagination — the server rejects the cursor
(pagination.cursor_invalid) rather than silently reshuffling results.
Error handling
Every non-2xx response is an RFC 9457 problem, surfaced as a typed error.
error.code is a stable machine-readable string and error.requestId is the
handle to quote when contacting support.
import {
APIError,
APIConnectionError,
AuthenticationError,
PermissionDeniedError,
InsufficientCreditsError,
RateLimitError,
BadRequestError,
DspValidationError,
ErrorCode,
} from '@anisprouts/dsp-sdk-trial';
try {
await client.search({ query: 'fintech' });
} catch (err) {
if (err instanceof RateLimitError) {
// 429 — quota.* ; err.retryAfter is the Retry-After header in seconds
} else if (err instanceof InsufficientCreditsError) {
// 402 credits.insufficient — waiting does not help; top up credits
} else if (err instanceof PermissionDeniedError) {
// 403 — usually auth.insufficient_scope: the key lacks a scope
} else if (err instanceof AuthenticationError) {
// 401 — key missing, unknown, revoked, or expired
} else if (err instanceof BadRequestError) {
// 400/422 — fix the request; err.problem.extra may carry specifics
} else if (err instanceof APIConnectionError) {
// network failure or timeout — no response was received
} else if (err instanceof DspValidationError) {
// the SDK rejected the call locally; nothing was sent
}
}| Error class | Status | Typical code |
|---|---|---|
| DspValidationError | — (not sent) | — |
| BadRequestError | 400 / 422 | request.invalid, pagination.cursor_invalid |
| AuthenticationError | 401 | auth.unauthorized, auth.invalid_key, auth.expired |
| InsufficientCreditsError | 402 | credits.insufficient |
| PermissionDeniedError | 403 | auth.insufficient_scope, auth.browser_origin |
| NotFoundError | 404 | — |
| ConflictError | 409 | idempotency.* |
| RateLimitError | 429 | quota.* (has retryAfter) |
| InternalServerError | 5xx | upstream.*, auth.unavailable, internal |
| APITimeoutError | — | request exceeded timeoutMs |
| APIConnectionError | — | network failure |
The full closed set of codes is exported as ErrorCode.
Retries, timeouts, aborts
Connection errors, timeouts, and 429 / 502 / 503 / 504 responses are retried
with exponential backoff and jitter, honoring Retry-After. Defaults: 2
retries, 60 s per attempt. All endpoints here are reads, so retries cannot
double-charge.
const client = new DspClient({ maxRetries: 3, timeoutMs: 30_000 });
// Per request:
const controller = new AbortController();
await client.search(
{ query: 'fintech' },
{ maxRetries: 0, timeoutMs: 5_000, signal: controller.signal },
);Reading responses honestly
The envelope carries fields worth checking, not just results:
total_is_estimate— when true,totalis an estimate.degradation[]— what the server couldn't do for this request (a corpus timed out, vector coverage incomplete, pagination truncated…). Empty means "checked, nothing degraded".meta.environment—liveortest, echoed so a test-key result can never be mistaken for a live one.meta.request_id— quote it in any support conversation.
Development
npm install
npm run typecheck
npm test
npm run build