Skip to main content
Seyn ships two official SDKs wrapping the v1 API in typed methods: TypeScript (private beta) and Python (early alpha). This page documents the TypeScript surface in full; the Python SDK mirrors it one-to-one, so the same reference applies.

Installation

See the Quickstart for install paths. Neither SDK is on a public registry yet: contact support@seynlabs.com for access until npm install @seyn/sdk and pip install seyn-sdk go live.

Python SDK (early alpha)

The Python SDK exposes the same four resources with the same semantics, in Python idiom: snake_case arguments and fields (top_k, review_status), typed result objects, and SeynError carrying the same error codes.
import os
from seyn import SeynClient

seyn = SeynClient(api_key=os.environ["SEYN_API_KEY"])

result = seyn.knowledge.query(q="When does a loan need senior approval?", top_k=5)
for hit in result.results:
    print(f"[{hit.score:.2f}] {hit.description}")

trail = seyn.rules.provenance(result.results[0].rule_id)
print(f"{len(trail.source_events)} events, {len(trail.raw_records)} raw records")
Early alpha means the method surface matches this reference, but Python-side ergonomics (async client, retries, pagination helpers) are still moving. Pin the exact version you receive and read the changelog that ships with each build.
Everything below is the canonical reference, shown in TypeScript. Translate names mechanically: topK becomes top_k, reviewStatus becomes review_status, and methods are synchronous in Python.

Client

new SeynClient(options)

import { SeynClient } from "@seyn/sdk";

const client = new SeynClient({
  apiKey: process.env.SEYN_API_KEY!,
  // baseUrl: "https://api-dev.seynlabs.com",  // optional, defaults to api.seynlabs.com
});
OptionTypeDefaultNotes
apiKeystring(none)Required. sk_live_* token from the dashboard.
baseUrlstringhttps://api.seynlabs.comOverride for staging or local development.
Throws synchronously if apiKey is empty or whitespace.

Resources

The client exposes four resource objects: Every method accepts an optional signal?: AbortSignal for cancellation/timeouts.

Knowledge

client.knowledge.query(options)

GET /v1/knowledge/query: runs a natural-language search.
const result = await client.knowledge.query({
  q: "When does a loan need senior approval?",
  topK: 5,
  strategy: "hybrid",          // optional: "auto" | "structured" | "hybrid" | "graph"
  libraryId: "uuid-here",       // optional: limit to one library
  includeExplain: true,         // optional: include the retrieval explain blob
});

// result.results is V1KnowledgeQueryHit[]
for (const hit of result.results) {
  console.log(`[${hit.score.toFixed(2)}] ${hit.description}`);
}
Returns Promise<V1KnowledgeQueryResult>:
{
  results: Array<{
    ruleId: string;
    description: string;
    processId: string | null;
    processName: string | null;
    step: string | null;
    confidence: number;
    score: number;
  }>;
  explain?: V1RetrievalExplain;  // present only if includeExplain was true
}

client.knowledge.memory(options)

POST /v1/knowledge/memory: teach Seyn something in plain language. Send a sentence describing what you want the knowledge base to know; Seyn extracts the claims, reconciles them against what it already believes, and writes them as assertions. Processing is async, attributed as a human edit. Needs an ingest-scoped key.
const memory = await client.knowledge.memory({
  text: "Deals over $50M always need board sign-off, not just the CEO.",
  // libraryId: "uuid-here",   // optional: defaults to the active library
});

// poll until applied
const status = await client.knowledge.memoryStatus(memory.memoryId);
if (status.status === "applied") {
  console.log(status.summary);
  console.log(`${status.assertionsCreated} created, ${status.assertionsSuperseded} superseded`);
}
Returns Promise<V1MemorySubmission> with memoryId and status. Throws SeynError with code: "INSUFFICIENT_SCOPE" for a read-only key, or "LIBRARY_NOT_FOUND" for an unknown libraryId.

Rules

client.rules.list(options?)

GET /v1/rules: paginated list of rules in the org.
const rules = await client.rules.list({
  reviewStatus: "confirmed",   // "inferred" | "confirmed" | "modified" | "rejected"
  libraryId: "uuid-here",
  search: "approval",
  limit: 50,
  offset: 0,
});
Returns Promise<V1RuleSummary[]>.

client.rules.get(id)

GET /v1/rules/:id: full detail for a single rule.
const rule = await client.rules.get("7f3cb37f-7d67-43f1-a3eb-a95ffdafb6b9");
console.log(rule.condition);   // structured logic tree
console.log(rule.evidence);    // supporting evidence
Returns Promise<V1RuleDetail>. Throws SeynError with code: "NOT_FOUND" if the rule doesn’t exist in the caller’s org.

client.rules.provenance(id)

GET /v1/rules/:id/provenance: the full audit chain back to source records.
const trail = await client.rules.provenance(rule.id);

console.log(trail.rule.description);
console.log(`Produced by ${trail.inferenceLog?.model}`);
console.log(`From ${trail.sourceEvents.length} events,`);
console.log(`across ${trail.rawRecords.length} raw records`);
console.log(`ingested via ${trail.rawRecords[0]?.connectorType}`);
Returns Promise<V1Provenance>. Throws SeynError with code: "NOT_FOUND" if the rule doesn’t exist.
Provenance responses can be large (KBs to tens of KBs). Each sourceEvents or rawRecords array can contain dozens of entries: that’s the point. If you only need a summary, just count the arrays.

Libraries

client.libraries.list(options?)

GET /v1/libraries: list versioned knowledge libraries.
const libraries = await client.libraries.list({
  status: "active",   // "draft" | "active" | "archived"
  limit: 20,
});
Returns Promise<V1LibrarySummary[]>.

client.libraries.get(id)

GET /v1/libraries/:id: single library detail.
const lib = await client.libraries.get("uuid-here");
console.log(`Library v${lib.version}, status=${lib.status}`);
Returns Promise<V1LibraryDetail>. Throws SeynError with code: "LIBRARY_NOT_FOUND" if missing.

client.libraries.rules(libraryId, options?)

GET /v1/libraries/:id/rules: rules scoped to one library.
const rules = await client.libraries.rules("library-uuid", {
  reviewStatus: "confirmed",
  limit: 100,
});
Returns Promise<V1RuleSummary[]>. Throws SeynError with code: "LIBRARY_NOT_FOUND" if the library doesn’t exist.

Patterns

client.patterns.metrics()

GET /v1/patterns/metrics: aggregate pipeline analytics.
const m = await client.patterns.metrics();

console.log(`${m.totalDeals} total deals, ${m.totalDeadDeals} dead`);
console.log("Median days in each stage:");
for (const row of m.stageVelocity) {
  console.log(`  ${row.stage}: ${row.medianDays}d (p90 ${row.p90Days}d)`);
}
Returns Promise<V1PatternMetrics>. See Core Concepts → Pattern Metrics for what each array means.

Ingestion

Reading is the bulk of the v1 surface, but you can also push data in. The ingestion methods need an API key with the ingest scope (request one from support); read-only keys get INSUFFICIENT_SCOPE. See Connectors → Build your own for the model.

client.sources.create(options)

POST /v1/sources: register a custom source to ingest from any system.
const source = await client.sources.create({
  name: "Internal CRM",
  entityTypes: ["deal", "account", "activity"],
});
Returns Promise<V1Source>. The source.id is what you ingest into.

client.ingest(options)

POST /v1/ingest: push a batch of records. Records are deduplicated by content hash and normalized asynchronously; reuse a recordId on update and Seyn supersedes the prior version. Up to 500 records per call.
const batch = await client.ingest({
  sourceId: source.id,
  records: [
    {
      recordId: "deal-4471",
      entityType: "deal",
      sourceUpdatedAt: "2026-06-10T14:03:00Z",
      payload: {
        name: "Acme Corp renewal",
        stage: "negotiation",
        amount: 42000,
        owner: "jane.smith@acme.com",
        movedToStageBy: "jane.smith@acme.com",
      },
    },
  ],
});

console.log(`${batch.accepted} accepted, ${batch.deduplicated} deduped`);
Returns Promise<V1IngestBatch> with batchId, accepted, deduplicated, and rejected[]. Throws SeynError with code: "SOURCE_NOT_FOUND" for an unknown source, or "PAYLOAD_TOO_LARGE" past the batch limits.

client.ingest.status(batchId)

GET /v1/ingest/{batchId}: poll a batch. Normalization is async, so a batch moves queued → processing → done.
const status = await client.ingest.status(batch.batchId);
if (status.status === "done") {
  console.log(`${status.recordsCreated} records, ${status.eventsCreated} events`);
}
Returns Promise<V1IngestBatch>.

Errors

Every non-2xx response throws SeynError:
import { SeynClient, SeynError } from "@seyn/sdk";

try {
  await client.rules.get("missing-id");
} catch (err) {
  if (err instanceof SeynError) {
    if (err.code === "NOT_FOUND") {
      return null;
    }
    if (err.code === "RATE_LIMITED") {
      // Back off for 60s before retrying. Replace with your own retry/backoff strategy.
      await new Promise((resolve) => setTimeout(resolve, 60_000));
      // retry...
    }
    if (err.code === "KEY_REVOKED") {
      // Wire this up to PagerDuty / Slack / whatever your team uses.
      console.error("Seyn key was revoked: rotate ASAP");
    }
    console.error(err.code, err.statusCode, err.message);
  }
  throw err;
}
SeynError shape:
class SeynError extends Error {
  code: V1ErrorCode | "UNKNOWN_ERROR";
  statusCode: number;
  requestId: string | undefined;  // include in support tickets
  message: string;
}
See Authentication → Error codes for the full table.

Cancellation and timeouts

Pass an AbortSignal on any method to cancel in-flight requests:
const ac = new AbortController();
setTimeout(() => ac.abort(), 5_000);

try {
  const rules = await client.rules.list({ signal: ac.signal });
} catch (err) {
  if (err.name === "AbortError") {
    console.log("query took too long, gave up");
  }
}
For per-call timeouts on Node 20+:
await client.rules.list({ signal: AbortSignal.timeout(5_000) });
The SDK does not provide built-in timeouts or retries: bring your own.

What’s not in the SDK

  • Knowledge writes: you can ingest source data, but rules and libraries are produced by extraction, not written directly.
  • Auto-pagination helpers: use limit/offset directly.
  • Server-sent events / streaming: not part of v1.
  • Browser bundle: Node-first, and ingest-scoped keys must stay server-side. For browser use, proxy through your own backend.