# @signaliz/sdk

Typed JavaScript/TypeScript access to all five core products, Signal
Awareness, Signaliz Flow, and delivered managed builds through one public contract.

```bash
npm install @signaliz/sdk
```

Production REST endpoints:

- `https://api.signaliz.com/functions/v1/api/v1/find-email`
- `https://api.signaliz.com/functions/v1/api/v1/verify-email`
- `https://api.signaliz.com/functions/v1/company-signal-enrichment-v2`
- `https://api.signaliz.com/functions/v1/api/v1/signal-to-copy`
- `https://api.signaliz.com/functions/v1/api/v1/signals-first` (`/signals` remains a compatibility alias)
- `https://api.signaliz.com/functions/v1/api/v1/signal-awareness/signals`
- `https://api.signaliz.com/functions/v1/api/v1/signal-awareness/companies/add`
- `https://api.signaliz.com/functions/v1/api/v1/signal-awareness/companies/delete`
- `https://api.signaliz.com/functions/v1/api/v1/signal-awareness/companies/schedule`
- `https://api.signaliz.com/functions/v1/api/v1/flow`
- `https://api.signaliz.com/functions/v1/api/v1/managed-builds`

The same request and response schemas are available under `/api/v2/`.

```ts
import { Signaliz } from '@signaliz/sdk';

const signaliz = new Signaliz({
  apiKey: process.env.SIGNALIZ_API_KEY,
});

// Every product supports a strict no-spend preflight. Batch methods accept the
// same option and return one plan for the full input.
const plan = await signaliz.findEmail({
  companyDomain: 'example.com',
  fullName: 'Jane Doe',
  dryRun: true,
});
console.log(plan.estimatedCredits.max, plan.creditsCharged); // 2, 0
// Batch plans price only semantic unique work and expose duplicate suppression.
console.log(plan.inputCount, plan.uniqueFreshInputCount, plan.inputDuplicatesSuppressed);

const found = await signaliz.findEmail({
  companyDomain: 'example.com',
  fullName: 'Jane Doe',
});

// An HTTPS LinkedIn person profile URL (/in/<slug>) is also sufficient on its own.
const foundFromLinkedIn = await signaliz.findEmail({
  linkedinUrl: 'https://www.linkedin.com/in/jane-doe',
});

const verified = await signaliz.verifyEmail(found.email!, {
});

// Syntax failures are terminal local results, with no HTTP or provider call.
const malformed = await signaliz.verifyEmail('badformat@@gmail..com');
console.log(malformed.success, malformed.isMalformed, malformed.verificationVerdict);

// The SDK waits for long checks by default. For an agent handoff, return early
// and resume the exact provider run later without duplicate spend.
const queuedVerification = await signaliz.verifyEmail('jane@example.com', {
  waitForResult: false,
});
const resumedVerification = await signaliz.verifyEmail('jane@example.com', {
  verificationRunId: queuedVerification.verificationRunId,
});

// Recovery reuses the original request and its public credit receipt.
console.log(resumedVerification.creditsUsed, resumedVerification.billingReplayed);

const signals = await signaliz.enrichCompanySignals({
  domain: 'example.com',
  researchPrompt: 'Find recent signals that explain why this company would use Signaliz.',
});
// Signal rows expose only the customer-facing signal fields.
console.log(
  signals.signals[0].signal_type,
  signals.signals[0].source,
  signals.signals[0].date,
);

// Signals First emits cached progress and follows the durable live-search
// shortfall to completion by default, matching the signed-in UI.
const progress = [];
const completedDiscovery = await signaliz.discoverSignalsFirst({
  query: 'companies that just got funded',
  limit: 100,
  onProgress: (receipt) => progress.push(receipt),
});
console.log(completedDiscovery.signals[0]?.domain);

// Automation can request the first queued/cached receipt without waiting.
const signalDiscovery = await signaliz.discoverSignalsFirst({
  query: 'companies that just got funded',
  limit: 100,
  waitForResult: false,
});

const copy = await signaliz.signalToCopy({
  companyDomain: 'example.com',
  personName: 'Jane Doe',
  title: 'VP Sales',
  campaignOffer: 'pipeline intelligence',
  researchPrompt: 'expansion priorities',
  enableDeepSearch: false,
});

const offerLedCopy = await signaliz.signalToCopy({
  companyDomain: 'example.com',
  personName: 'Jane Doe',
  title: 'VP Sales',
  campaignOffer: 'pipeline intelligence',
  runWithoutSignal: true,
});

await signaliz.addSignalAwarenessCompany('acme.com', 'daily');
await signaliz.changeSignalAwarenessSchedule('acme.com', 'weekly');
const awareness = await signaliz.listAllSignalAwarenessSignals('acme.com');
console.log(
  awareness.signals?.[0].signal_type,
  awareness.signals?.[0].source,
  awareness.signals?.[0].date,
  awareness.signals?.[0].company_name,
  awareness.signals?.[0].domain,
);
await signaliz.deleteSignalAwarenessCompany('acme.com');

// Flow uses the same versioned REST gateway as every other SDK capability.
const flow = await signaliz.startFlow({
  signalQuery: 'companies with recent hiring',
  campaignOffer: 'pipeline intelligence',
});
const flowStatus = await signaliz.getFlow(flow.runId);
// Re-call getFlow with the same run ID until status is completed or failed.
// Flow leaves generated copy in review and never sends outreach.

// Delivered GTM Contractor builds are workspace-owned, reusable systems.
const delivered = await signaliz.listManagedBuilds();
const build = await signaliz.getManagedBuild(delivered.builds[0].build_id);
console.log(build.build.fixed_credits_per_run, build.build.clay_credits_budget_per_run);
const managedRun = await signaliz.runManagedBuild(build.build.build_id, {
  records: [{ domain: 'example.com' }],
});
const managedResult = await signaliz.getManagedBuildRun(managedRun.run.run_id);
// Supply the same idempotency key only to recover that exact intended run.

// Company Signals and Signal to Copy are hard synchronous contracts: these
// methods return complete data, an explicit success:false result, or throw a
// non-2xx terminal error in this same call.
```

The four row-oriented products support bounded batch execution over the same REST endpoints.
Results stay in input order, failures are isolated per item, and concurrency is
limited to `1-50` (default `10`):

```ts
const verifiedBatch = await signaliz.verifyEmails(emails, { concurrency: 20 });
const foundBatch = await signaliz.findEmails(contacts, { concurrency: 20 });
const signalBatch = await signaliz.enrichCompanies(companies, { concurrency: 5 });
const copyBatch = await signaliz.createSignalCopyBatch(copyRequests, { concurrency: 5 });

// If a timeout or interrupted client returns a durable job ID, resume only
// that job. These methods never submit `requests` or repeat provider work.
const resumedFoundBatch = await signaliz.resumeFindEmailBatch('job_...');
const resumedVerifiedBatch = await signaliz.resumeVerifyEmailBatch('job_...');
const resumedSignalBatch = await signaliz.resumeCompanySignalBatch('job_...');
const resumedCopyBatch = await signaliz.resumeSignalCopyBatch('job_...');

// Long Company/Copy batches can return immediately with the actual durable
// job ID and idempotency key. Persist both before yielding the agent turn.
const signalJob = await signaliz.enrichCompanies(companies, {
  waitForResult: false,
});
// { jobId, idempotencyKey, status, total, ... }

// Keep one full result for exact repeats and return duplicateOf references for
// the rest. This is lossless and avoids multiplying evidence-rich payloads.
const compactSignals = await signaliz.enrichCompanies(companies, {
  concurrency: 5,
  compactDuplicates: true,
});
```

Failed rows preserve `errorCode`, `retryEligible`, and `retryAfterSeconds`
when the REST API supplies them, including after automatic row retries are
exhausted.

Every single-result method treats an error payload as `success: false`, even if
an upstream HTTP 200 omits or contradicts the success flag. This includes
`ok: false`, non-empty error fields or error arrays, and terminal provider
statuses such as `timed_out` or `crashed`; an informational `message` alone is
not an error. Find Email withholds the email and every send-safe indicator,
Verify Email withholds deliverability and confidence, and Company Signals and
Signal to Copy withhold contradictory signals, narratives, and copy. Normalized
`error` and `errorCode` fields explain the failure while the original response
remains available in `raw` for audit.

For an ambiguous single request or batch submission failure, retry with the
same `idempotencyKey` to recover the original request, durable job, or provider
run without duplicate work or spend. The key is portable across REST API, MCP,
SDK, and CLI calls for the same workspace and logical input. Chunked batches
derive a stable key from each row's original input index, including after exact
duplicate compaction and rate-limit retries.

Each batch accepts up to 5,000 items. For the four row-oriented core products,
batches larger than 25 use one recoverable REST job. The SDK waits by default
and retrieves every result page. Large Company Signals and Signal to Copy calls
can instead set `waitForResult: false` to receive `jobId` plus the actual
`idempotencyKey`, then resume with `resumeCompanySignalBatch` or
`resumeSignalCopyBatch`. Company/Copy polling has no fixed SDK deadline unless
`maxWaitMs` is explicitly supplied; timeout errors retain both recovery fields.
Find Email and Verify Email polling defaults to 20 minutes; large email batches
honor `maxWaitMs` and `pollIntervalMs` and retain the same recovery fields.
Find Email, Verify Email, and Company Signals use bounded 25-row result pages;
Signal to Copy uses 100-row durable pages.
Retrieve completed Company/Copy pages within seven days. After cleanup, recovery
fails with non-retryable `BATCH_RESULTS_EXPIRED`; do not automatically resubmit
provider work, and require an explicit review before starting a new job.
Company Signal `signal_run_id` recovery rows remain direct batches of at most
25, matching the recovery-read API contract instead of starting a new durable
research job.
Large Company Signal jobs persist bounded result pages and intentionally omit
rejected candidate-evidence diagnostics. Requests with
`includeCandidateEvidence: true` must be split into batches of at most 25.
Exact duplicate tasks are single-flighted for synchronous batches. Durable
Company/Copy submissions preserve every input row and its index so the server's
idempotency identity remains stable across REST, MCP, SDK, and CLI recovery.
Set `compactDuplicates: true` to return repeated successful rows as
`duplicateOf` references; the default remains expanded for compatibility.
Signal to Copy durable jobs complete missing company-signal research before
generating copy. They accept uniform `enableDeepSearch`
controls for the full job. Rows still return `SIGNAL_DATA_REQUIRED` when
research completes without qualifying evidence.
Signal to Copy also shares company research across copy recipients.
Single calls and synchronous batches of at most 25 may instead pass
`signalSearchRunId`, `factId`, or `eventId` to reuse canonical signal evidence.
Durable batches of 26-5,000 reject these references rather than silently
starting new research.

Signals First is query-first rather than a row batch. It defaults to 100
distinct companies and supports up to 100. Every returned signal row contains
only a signal type, verified source URL, event date, company name, and domain.
A request is a ceiling, not a promise: it can return fewer results
when public evidence cannot verify both a date and company identity. It is
included on Builder, Team, and Agency plans and can be resumed with
`signalSearchRunId`. `discoverSignalsFirst` follows queued and cached-progress
receipts by default; set `waitForResult: false` for a durable asynchronous
handoff. The compatibility `discoverSignals` method remains single-request.

Company Signal Enrichment uses one fixed provider policy: cache first, one
type-agnostic Parallel Advanced search, and at most one Extract of a
first-party multi-event hub. Deterministic code enforces company identity,
retrieved URLs, source quality, content-proven dates, copied evidence,
lookback, and event deduplication before returning the newest ten events. It
does not expose provider, model, prompt, lookback, signal-type, cache, or
target-count controls.
Signal to Copy AI returns three
complete, evidence-backed email variations when supported signals are available.
Signal to Copy defaults `enableDeepSearch` to `false` so it normally finishes
within an agent turn; enable it only when deeper research is required.
Every core request uses the authenticated workspace. Find Email costs 1 credit
per returned result and Verify Email costs 0.02 credits per returned terminal
result on every plan. Signaliz automatically reuses eligible evidence and
prevents duplicate provider work. Builder, Team, Agency, and Pay-As-You-Go
include Company Signals.
Signal to Copy costs 1 credit per successful result on Free and Builder, and is
unlimited on Team and Agency. `lookbackDays` remains a hard evidence-age bound.
Unclassified evidence is returned by signal enrichment but is not used to
generate outreach copy.
Provider, billing provenance, routing, and validation internals are intentionally
omitted from public signal rows.

`listTools()` and `health()` are connection helpers, not additional products.

## Campaign OS agent bridge

Campaign OS is an admin-only, agent-facing control plane. Codex, Claude Code,
Cursor, Devin, Grok, Signaliz Agent, or another harness can use the same SDK
methods to read a bounded client Brain, autonomously build a complete
provider-locked CampaignSpec, draft an Audience, compile no-call provider
previews, submit consequential action requests, and inspect receipts:

```ts
const clients = await signaliz.listCampaignOsClients({ status: 'active' });
const context = await signaliz.getCampaignOsClientContext(clients.clients[0].id);
const memory = await signaliz.searchCampaignOsKnowledge({
  query: 'cache first suppression and verification',
  recordKind: 'campaign_playbook',
});
const build = await signaliz.startCampaignOsBuild({
  clientId: clients.clients[0].id,
  name: 'Realtime product launch',
  objective: 'Build an evidence-grounded 500-person campaign',
  targetCount: 500,
  agentVendor: 'codex',
  idempotencyKey: 'launch-build-v1',
});
const draft = await signaliz.createCampaignOsDraft({
  clientId: clients.clients[0].id,
  name: 'Realtime product launch',
  lane: 'realtime',
  campaignSpec,
  agentVendor: 'codex',
  idempotencyKey: 'launch-draft-v1',
});
const materialization = await signaliz.materializeCampaignOsCacheAudience({
  clientId: clients.clients[0].id,
  audienceId: '<audience-uuid>',
  campaignId: draft.campaign.id,
  targetCount: 500,
  idempotencyKey: 'launch-cache-materialization-v1',
});
const alphaPreflight = await signaliz.preflightCampaignOsAlphaMaterialization({
  clientId: clients.clients[0].id,
  campaignId: draft.campaign.id,
  audienceRunId: materialization.run.id,
  validationRunId: '<passing-alpha-validation-run-uuid>',
  idempotencyKey: 'launch-alpha-materialization-v1',
});
const alphaRun = await signaliz.startCampaignOsAlphaMaterialization({
  materializationRunId: String(alphaPreflight.run.id),
  approvalHash: String(alphaPreflight.run.approval_hash),
  confirmSpend: true,
});
const alphaStatus = await signaliz.getCampaignOsAlphaMaterialization(
  String(alphaRun.run.id),
);
const request = await signaliz.submitCampaignOsActionRequest({
  clientId: clients.clients[0].id,
  campaignId: draft.campaign.id,
  actionType: 'provider_load',
  instructions: 'Prepare this exact provider load for human review.',
  preflightInputs: {
    provider: 'instantly',
    providerArtifactId: '<provider-artifact-uuid>',
    audienceRunId: '<audience-run-uuid>',
  },
  idempotencyKey: 'launch-provider-load-request-v1',
});
// After a Signaliz superadmin approves the proposal:
const continuation = await signaliz.prepareCampaignOsActionPreflight(
  clients.clients[0].id,
  request.request.id,
);
```

The Agent Bridge never calls Clay, Instantly, Smartlead, or Bison while building,
drafting or cache-materializing an Audience, or compiling a preview. Cache-only
materialization is capped at 500 members and returns exact zero-provider/Clay
usage plus a terminal reconciliation receipt. Supplying `campaignId` links that
run to the exact campaign lifecycle. Those safe operations do not need
CMM-admin approval. Drafts keep paid sourcing, provider load, activation,
real-time arming, queueing, and sending locked. `submitCampaignOsActionRequest()`
requires a nonblank idempotency key and records intent for a fresh human decision
at one of those consequential boundaries; even `provider_send` records a request
and does not execute delivery. It stores only whitelisted non-secret preflight
inputs. After human approval,
`prepareCampaignOsActionPreflight()` appends or reads one immutable no-call
continuation with explicit missing fields and adapter blockers; it still does
not perform the downstream preflight or any provider mutation.

Alpha materialization is the one bounded exception to the no-spend build path:
its preflight is free, while `startCampaignOsAlphaMaterialization()` authorizes
only the exact research ceiling already pinned in its immutable approval hash.
It never calls Clay, creates an Audience, loads or activates a provider, queues,
or sends.
