The official JavaScript/TypeScript SDK for the Seclai API. Provides full typed coverage of all API endpoints, file uploads, SSE streaming, polling helpers, and automatic pagination.
Works in Node.js 18+, Deno, Bun, Cloudflare Workers, and any runtime with a fetch implementation.
npm install @seclai/sdkimport { Seclai } from "@seclai/sdk";
const client = new Seclai({ apiKey: process.env.SECLAI_API_KEY });
// List all sources
const sources = await client.listSources();
// Run an agent and stream the result
const result = await client.runStreamingAgentAndWait(
"agent_id",
{ input: "Summarize the latest uploads", metadata: {} },
{ timeoutMs: 120_000 },
);
console.log(result);| Option | Environment variable | Default |
|---|---|---|
apiKey |
SECLAI_API_KEY |
— |
accessToken |
— | — |
profile |
SECLAI_PROFILE |
"default" |
configDir |
SECLAI_CONFIG_DIR |
~/.seclai |
autoRefresh |
— | true |
accountId |
— | — |
baseUrl |
SECLAI_API_URL |
https://api.seclai.com |
apiKeyHeader |
— | x-api-key |
defaultHeaders |
— | {} |
fetch |
— | globalThis.fetch |
Credentials are resolved via a chain (first match wins):
- Explicit
apiKeyoption - Explicit
accessTokenoption (string or() => string | Promise<string>) SECLAI_API_KEYenvironment variable- SSO — cached tokens from
~/.seclai/sso/cache/(requires a priorseclai auth login)
// API key
const client = new Seclai({ apiKey: "sk-..." });// Static bearer token
const client = new Seclai({ accessToken: "eyJhbGciOi..." });// Dynamic bearer token provider (called per request)
const client = new Seclai({
accessToken: async () => fetchTokenFromVault(),
});// SSO profile (uses cached tokens, auto-refreshes)
const client = new Seclai({ profile: "my-profile" });// Environment variable (no options needed)
// export SECLAI_API_KEY="sk-..."
const client = new Seclai();SSO is the default fallback when no explicit credentials are provided. The SDK
includes built-in production SSO defaults, so seclai configure sso is not
required. You only need to log in once to populate the token cache:
npx @seclai/cli auth login # authenticate via browser — no prior setup neededTo customize SSO settings (e.g. for a staging environment), use seclai configure sso
or set environment variables:
| Variable | Description | Default |
|---|---|---|
SECLAI_SSO_DOMAIN |
Cognito domain | auth.seclai.com |
SECLAI_SSO_CLIENT_ID |
Cognito app client ID | 4bgf8v9qmc5puivbaqon9n5lmr |
SECLAI_SSO_REGION |
AWS region | us-west-2 |
Online API documentation (latest):
https://seclai.github.io/seclai-javascript/latest/
Release history is in CHANGELOG.md.
The API dates its backward-incompatible changes. Nothing changes for you until you opt in, either per client or by pinning the account:
const client = new Seclai({
apiKey: "...",
apiVersion: SeclaiApiVersion.V2026_07_27, // sent as the Seclai-Version header
});
const state = await client.getApiVersion(); // what this request resolved to
await client.updateApiVersion(SeclaiApiVersion.V2026_07_27); // pin the whole accountLeave apiVersion unset and the header is omitted, so the account's pinned
baseline applies and responses keep their current shapes. Upgrading this package
alone never changes the wire contract.
Known versions are on SeclaiApiVersion (V2026_07_01 through V2026_10_03,
plus Default and Latest), imported from @seclai/sdk. A version this release was
not built against throws at construction: a newer version can reshape
responses, and this client would decode them incorrectly rather than reject them.
The same check applies to a Seclai-Version set through defaultHeaders or the
per-request headers of request() / requestRaw(), in any letter case, and an
empty value is rejected. Upgrade the package to adopt a new version, or set
allowUnknownApiVersion if you have to move first and accept that risk.
The guard only covers the header. An account pinned server-side can still be
newer than this release — getApiVersion() reports the effective_version the
request resolved to, and comparing it against SeclaiApiVersion.Latest is how
you detect the gap.
What 2026-07-27 changes. Undeclared query parameters become a 422 instead
of being ignored, and every list endpoint that answered with a bare array or
under a per-resource key moves to the canonical {data, pagination} envelope.
The methods for those endpoints return their declared type on either shape, so
code written against the default still reads the result after you opt in. Four
of them return fewer rows once you do, covered below the table:
| Declared return | Methods | From 2026-07-27 |
|---|---|---|
| An array | listEvaluationCriteria(), getAgentCallers(), listInboundEmailRejections(), listGovernanceAiConversations(), listSolutionConversations(), listModels(), listMemoryBankTemplates(), getAgentsUsingMemoryBank(), listCloudDriveProviders(), listCloudDrives(), getAgentsUsingCloudDrive(), listCloudDriveRejections() |
Still the array of items; the page metadata is not returned |
data, bare array by default |
listEvaluationCriteriaPage(), listRunEvaluationResults() |
data, plus pagination |
data with flat total/page/limit |
listEvaluationResults(), listAgentEvaluationResults(), listEvaluationRuns(), listCompatibleRuns() |
Unchanged, plus pagination |
| A per-resource key | listAgentEmailOptOuts() and listBlockedEmailSenders() / setAutoBlockMode() (items), listAlertConfigs() (configs), listOrganizationAlertPreferences() (preferences), listEmailDomains() (domains), listKnowledgeBases() (knowledge_bases), listMemoryBanks() (memory_banks), getGenerationTiers() (tiers), listModelAlerts() (alerts), listExperiments() (experiments), listEmbeddingModels() and listRerankerModels() (models) |
The same key and any flat total/page/limit, plus data and pagination |
Where a type declares flat total, page or limit, the client fills them
from pagination after you opt in. listRunEvaluationResults() has no counters
on its default bare array, and gains them with pagination. Fields that sit
beside a list, such as auto_block_mode or the email-domain plan capabilities,
are present on both shapes.
Opting in also turns paging on for endpoints that returned everything by default, so the same call can return fewer rows:
listEvaluationCriteria()andlistRunEvaluationResults()return every item by default and one page (20 unless you passlimit) after you opt in. The array fromlistEvaluationCriteria()carries no sign of that; uselistEvaluationCriteriaPage()to seepagination.listAlertConfigs()ignorespageandlimitby default and returns every config; after you opt in it returns one page.setAutoBlockMode()reports the account's fulltotalby default, and the number of rows it returned after you opt in.
Later versions. Each is cumulative. None changes a response shape this
client decodes, but 2026-09-30 changes what a string you may be parsing
contains:
| Version | What it changes |
|---|---|
2026-08-03 |
createMemoryBank() and updateMemoryBank() reject a non-zero max_age_days with a 400, and a memory bank's max_age_days reads as null. On create, an omitted retention_days resolves per bank type instead of to 30 |
2026-08-21 |
createSource() rejects an embedding dimension its embedder does not support with a 400 — listEmbeddingModels() reports the supported ones |
2026-09-28 |
Agent-definition writes use the current file-list grammar: an omitted attachments keeps the stored list and [] means no files |
2026-09-30 |
Breaks code that parses output. A run's and a step's output, and a step's input, are the plain text; below this version an output that has files is a JSON manifest string ({schema, text, attachments}). Read files from attachments, which is populated on every version |
2026-10-03 |
A new LLM step written without attachments takes its parent's files, and a new retrieval step's matched media are its files |
Latest moves with the SDK. SeclaiApiVersion.Latest is the newest version
the installed release knows, so upgrading the package can opt a client that
passes it into every version added since — 1.6.0 moved it from 2026-07-27 to
2026-10-03, across the 2026-09-30 output change. Pass a dated constant such
as SeclaiApiVersion.V2026_07_27 to keep behaviour fixed across upgrades.
Discover who the credential belongs to and which organizations it can act for.
Each organization's account_id is what you pass as the client's accountId
option (sent as the X-Account-Id header) to target that org context.
const me = await client.getMe();
console.log(me.account_id); // the user's personal account
for (const org of me.organizations) {
console.log(org.name, org.account_id);
}
// Act as an organization
const orgClient = new Seclai({ accountId: me.organizations[0].account_id });// CRUD
const agents = await client.listAgents({ page: 1, limit: 20 });
const agent = await client.createAgent({ name: "My Agent", description: "..." });
const fetched = await client.getAgent("agent_id");
const updated = await client.updateAgent("agent_id", { name: "Renamed" });
await client.deleteAgent("agent_id");
// Pause / resume — a disabled agent stops firing from every trigger path
const callers = await client.getAgentCallers("agent_id"); // live agents calling this one
await client.disableAgent("agent_id"); // 409 if any caller above is still live
await client.enableAgent("agent_id");
// Definition (step workflow)
const def = await client.getAgentDefinition("agent_id");
await client.updateAgentDefinition("agent_id", { steps: [...], change_id: def.change_id });
// Export / import an agent
const exported = await client.exportAgent("agent_id");
// Validate the payload first to surface unresolved entity refs in this account
const preview = await client.previewImportAgent({ agent_definition: exported });
const unresolved = (preview.unresolved_refs ?? []) as Array<{ ref_id: string }>;
const entity_remap = Object.fromEntries(
unresolved.map((ref) => [ref.ref_id, /* picked target uuid */ ""]),
);
// Commit — `entity_remap` substitutes workflow refs before save
const imported = await client.createAgent({
name: "Imported",
trigger_type: "dynamic_input",
agent_definition: exported,
entity_remap,
});
// `imported.import_warnings` lists any items that couldn't be applied.// Start a run
const run = await client.runAgent("agent_id", { input: "Hello" });
// List & search runs
const runs = await client.listAgentRuns("agent_id", { status: "completed" });
const search = await client.searchAgentRuns({ agent_id: "...", status: ["completed"] });
// Fetch run details (optionally with step outputs)
const detail = await client.getAgentRun("run_id", { includeStepOutputs: true });
// Cancel or delete
await client.cancelAgentRun("run_id");
await client.deleteAgentRun("run_id");The SDK provides two streaming patterns over the SSE /runs/stream endpoint:
Block until done — returns the final done payload or throws on timeout:
const result = await client.runStreamingAgentAndWait(
"agent_id",
{ input: "Hello", metadata: {} },
{ timeoutMs: 60_000 },
);Async iterator — yields every SSE event as { event, data }:
for await (const event of client.runStreamingAgent(
"agent_id",
{ input: "Hello" },
{ timeoutMs: 120_000 },
)) {
console.log(event.event, event.data);
if (event.event === "done") break;
}For environments where SSE is not practical, poll for a completed run:
const result = await client.runAgentAndPoll(
"agent_id",
{ input: "Hello" },
{ pollIntervalMs: 2_000, timeoutMs: 120_000 },
);// Discover which files (if any) the agent expects before staging uploads
const refs = await client.getAgentAttachmentReferences("agent_id");
if (refs.requires_uploads) {
// refs.agent lists the exact_names / indexes_max / patterns a run batch must satisfy
}
const upload = await client.uploadAgentInput("agent_id", {
file: new Uint8Array([...]),
fileName: "input.pdf",
});
const status = await client.getAgentInputUploadStatus("agent_id", upload.upload_id);// Download a file emitted by a step in an agent run. The attachment_id is the
// URL-safe-base64 storage_key surfaced in run output manifests / webhooks.
const resp = await client.downloadAgentRunAttachment("run_id", "attachment_id");
const blob = await resp.blob(); // raw Response — stream or save the bytesConfigure the inbound address and handling rules on an agent's EMAIL_RECEIVED
trigger. Omitted fields are left unchanged; null (or "" for alias) clears them.
const config = await client.setEmailTriggerConfig("agent_id", "trigger_id", {
alias: "support",
allowed_senders: ["example.com", "ops@partner.com"], // empty/null accepts any sender
ignore_auto_generated: true, // drop auto-replies/bulk mail to prevent loops
require_sender_auth: true, // require SPF or DMARC even on an open inbox
queue_on_quota: false, // park over-rate mail instead of failing it
});
console.log(config.email_addresses); // ["support.<accountID>@agent.seclai.com", ...]// Recipients who opted out of this account's agent emails
const optOuts = await client.listAgentEmailOptOuts({ agentId: "agent_id", limit: 50 });
await client.removeAgentEmailOptOut("optout_id"); // opt them back in
// Blocked inbound senders (owner/admin only)
const blocked = await client.listBlockedEmailSenders({ limit: 50, offset: 0 });
await client.blockEmailSender({ sender_email: "spam.example.com", match_type: "domain" });
await client.unblockEmailSender("blocked_id");
// Auto-block on a governance BLOCK: "disabled" | "input" | "input_and_output"
await client.setAutoBlockMode({ mode: "input_and_output" });
// Inbound emails discarded before running an agent
const rejections = await client.listInboundEmailRejections({ agentId: "agent_id" });
// Account-wide overload circuit breaker
const status = await client.getInboundEmailStatus(); // { paused, queued_backlog }
await client.cancelQueuedEmailRuns(); // fail all QUEUED (over-quota parked) runs
await client.resumeInboundEmail(); // one-shot override; re-arms if still overloadedconst steps = await client.generateAgentSteps("agent_id", { user_input: "Build a RAG pipeline" });
const config = await client.generateStepConfig("agent_id", { step_type: "llm", user_input: "..." });
// Conversation history
const history = await client.getAgentAiConversationHistory("agent_id");
await client.markAgentAiSuggestion("agent_id", "conversation_id", { accepted: true });const criteria = await client.listEvaluationCriteria("agent_id", { page: 1, limit: 50 });
// page/limit only take effect with apiVersion "2026-07-27" or later; the legacy
// response is unpaginated. listEvaluationCriteriaPage() returns the same items
// plus a `pagination` object when opted in.
const created = await client.createEvaluationCriteria("agent_id", { name: "Accuracy", ... });
const detail = await client.getEvaluationCriteria("criteria_id");
await client.updateEvaluationCriteria("criteria_id", { ... });
await client.deleteEvaluationCriteria("criteria_id");
// Test a draft
await client.testDraftEvaluation("agent_id", { criteria: { ... }, run_id: "..." });
// Results by criteria
const results = await client.listEvaluationResults("criteria_id");
const summary = await client.getEvaluationCriteriaSummary("criteria_id");
await client.createEvaluationResult("criteria_id", { ... });
// Results by run
const runResults = await client.listRunEvaluationResults("agent_id", "run_id");
// Non-manual evaluation summary
const nonManual = await client.getNonManualEvaluationSummary("agent_id");
// Compatible runs for a criteria
const compatible = await client.listCompatibleRuns("criteria_id");const kbs = await client.listKnowledgeBases();
const kb = await client.createKnowledgeBase({ name: "Docs KB" });
const fetched = await client.getKnowledgeBase("kb_id");
await client.updateKnowledgeBase("kb_id", { name: "Renamed KB" });
await client.deleteKnowledgeBase("kb_id");const banks = await client.listMemoryBanks();
const bank = await client.createMemoryBank({ name: "Chat Memory", type: "conversation" });
const fetched = await client.getMemoryBank("mb_id");
await client.updateMemoryBank("mb_id", { name: "Renamed" });
await client.deleteMemoryBank("mb_id");
// Stats & compaction
const stats = await client.getMemoryBankStats("mb_id");
await client.compactMemoryBank("mb_id");
// Test compaction
const test = await client.testMemoryBankCompaction("mb_id", { ... });
const standalone = await client.testCompactionPromptStandalone({ ... });
// Templates & agents
const templates = await client.listMemoryBankTemplates();
const agents = await client.getAgentsUsingMemoryBank("mb_id");
// AI assistant
const suggestion = await client.generateMemoryBankConfig({ user_input: "..." });
const history = await client.getMemoryBankAiLastConversation();
await client.acceptMemoryBankAiSuggestion("conv_id", { ... });
// Source management
await client.deleteMemoryBankSource("mb_id");const sources = await client.listSources({ page: 1, limit: 20, order: "asc" });
const source = await client.createSource({ name: "My Source", ... });
const fetched = await client.getSource("source_id");
await client.updateSource("source_id", { name: "Renamed" });
await client.deleteSource("source_id");Indexing status of a source's content, keyed by the content_version_id the
upload methods return:
const failed = await client.listSourceContents("source_id", { status: "failed" });
const batch = await client.listSourceContents("source_id", {
contentVersionIds: ["cv_1", "cv_2"],
});
console.log(batch.pagination.total, failed.data.map((item) => item.error));
const one = await client.getSourceContentStatus("source_id", "cv_1");
console.log(one.content_status);const providers = await client.listCloudDriveProviders();
const drives = await client.listCloudDrives();
const drive = await client.getCloudDrive(drives[0].id);
await client.updateCloudDrive(drive.id, { name: "Contracts" });
// Which agents depend on it, and which files it skipped and why
const agents = await client.getAgentsUsingCloudDrive(drive.id);
const skipped = await client.listCloudDriveRejections(drive.id, { limit: 20 });
console.log(providers.length, agents.length, skipped.map((r) => r.reason));
const disconnected = await client.disconnectCloudDrive(drive.id); // keeps the connection
console.log(disconnected.connected);
await client.deleteCloudDrive(drive.id);Upload a file to a source (max 200 MiB). The SDK infers MIME type from the file extension when mimeType is not provided.
import { readFile } from "node:fs/promises";
await client.uploadFileToSource("source_id", {
file: await readFile("document.pdf"),
fileName: "document.pdf",
title: "Q4 Report",
metadata: { department: "finance" },
});Upload inline text:
await client.uploadInlineTextToSource("source_id", {
text: "Hello, world!",
title: "Greeting",
});Replace a content version with a new file:
await client.uploadFileToContent("content_version_id", {
file: await readFile("updated.pdf"),
fileName: "updated.pdf",
mimeType: "application/pdf",
});const exports = await client.listSourceExports("source_id");
const exp = await client.createSourceExport("source_id", { format: "json" });
const status = await client.getSourceExport("source_id", exp.id);
const estimate = await client.estimateSourceExport("source_id", {});
const response = await client.downloadSourceExport("source_id", exp.id);
await client.deleteSourceExport("source_id", exp.id);
await client.cancelSourceExport("source_id", exp.id);const migration = await client.getSourceEmbeddingMigration("source_id");
await client.startSourceEmbeddingMigration("source_id", { target_model: "..." });
await client.cancelSourceEmbeddingMigration("source_id");const detail = await client.getContentDetail("content_id", { start: 0, end: 1000 });
const embeddings = await client.listContentEmbeddings("content_id");
await client.deleteContent("content_id");
// Replace content with inline text
await client.replaceContentWithInlineText("content_id", { text: "Updated text", title: "Updated" });
// Upload a replacement file
await client.uploadFileToContent("content_id", {
file: await readFile("updated.pdf"),
fileName: "updated.pdf",
});const solutions = await client.listSolutions();
const sol = await client.createSolution({ name: "My Solution" });
const fetched = await client.getSolution("solution_id");
await client.updateSolution("solution_id", { name: "Renamed" });
await client.deleteSolution("solution_id");
// Link / unlink resources
await client.linkAgentsToSolution("solution_id", { ids: ["agent_id"] });
await client.unlinkAgentsFromSolution("solution_id", { ids: ["agent_id"] });
await client.linkKnowledgeBasesToSolution("solution_id", { ids: ["kb_id"] });
await client.unlinkKnowledgeBasesFromSolution("solution_id", { ids: ["kb_id"] });
await client.linkSourceConnectionsToSolution("solution_id", { ids: ["source_id"] });
await client.unlinkSourceConnectionsFromSolution("solution_id", { ids: ["source_id"] });
// AI assistant
const plan = await client.generateSolutionAiPlan("solution_id", { user_input: "Set up a RAG pipeline" });
await client.acceptSolutionAiPlan("solution_id", "conversation_id", {});
await client.declineSolutionAiPlan("solution_id", "conversation_id");
// AI-generated knowledge base / source within the solution
await client.generateSolutionAiKnowledgeBase("solution_id", { user_input: "..." });
await client.generateSolutionAiSource("solution_id", { user_input: "..." });
// Conversations
const convs = await client.listSolutionConversations("solution_id");
await client.addSolutionConversationTurn("solution_id", { user_input: "..." });
await client.markSolutionConversationTurn("solution_id", "conversation_id", { ... });const plan = await client.generateGovernanceAiPlan({ user_input: "Add a toxicity policy" });
const convs = await client.listGovernanceAiConversations();
await client.acceptGovernanceAiPlan("conversation_id");
await client.declineGovernanceAiPlan("conversation_id");const alerts = await client.listAlerts({ status: "active" });
const alert = await client.getAlert("alert_id");
await client.changeAlertStatus("alert_id", { status: "resolved" });
await client.addAlertComment("alert_id", { text: "Investigating" });
// Subscriptions
await client.subscribeToAlert("alert_id");
await client.unsubscribeFromAlert("alert_id");
// Alert configs
const configs = await client.listAlertConfigs();
await client.createAlertConfig({ ... });
await client.getAlertConfig("config_id");
await client.updateAlertConfig("config_id", { ... });
await client.deleteAlertConfig("config_id");
// Organization preferences
const prefs = await client.listOrganizationAlertPreferences();
await client.updateOrganizationAlertPreference("org_id", "alert_type", { ... });// List models, optionally filtered by capability
const providers = await client.listModels({ supportsToolUse: true });
const imageModels = await client.listModels({ supportsOutputMedia: "image" });
const pdfModels = await client.listModels({ supportsInputMedia: "pdf" });
const model = await client.getModel("model_id");
// Media-generation quality tiers (fast/balanced/thorough) and what each resolves to
const tiers = await client.getGenerationTiers();
// Embedding and reranker models, with their pricing
const embedders = await client.listEmbeddingModels({ supportsInputMedia: "image" });
const rerankers = await client.listRerankerModels();
console.log(embedders.models, embedders.default_model_type, rerankers.models);
const alerts = await client.listModelAlerts();
await client.markModelAlertRead("alert_id");
await client.markAllModelAlertsRead();
const unread = await client.getUnreadModelAlertCount();
const recs = await client.getModelRecommendations("model_id");
// Model playground experiments
const experiment = await client.createExperiment({ model_ids: ["model_id"], prompt: "..." });
const experiments = await client.listExperiments();
const detail = await client.getExperiment("experiment_id");
await client.cancelExperiment("experiment_id");
await client.deleteExperiment("experiment_id"); // soft-delete, preserves audit historyconst results = await client.search({ query: "quarterly report" });
const filtered = await client.search({ query: "my agent", entityType: "agent", limit: 5 });Search the Seclai docs. Results are global (not account-scoped) and each carries a
doc_slug plus an optional anchor for building a
https://seclai.com/docs/<doc_slug>[#<anchor>] link.
// Fast title/summary match, no AI cost
const hits = await client.searchDocs({ query: "email triggers" });
// Semantic body match — adds a `highlight` with the best matching sentence
const semantic = await client.searchDocs({
query: "how do I stop auto-reply loops",
mode: "semantic",
limit: 5,
});Send and receive agent email on your own domain instead of the shared
agent.seclai.com. These endpoints require a user-bound credential (an
account-only API key is refused with 403) and, for mutations, an account owner/admin.
// Current domains, their DNS records, and what your plan allows
const { domains, can_add_custom, has_custom } = await client.listEmailDomains();
// Add a vanity subdomain (<slug>.seclai.com) or your own custom domain
const vanity = await client.addEmailDomain({ kind: "vanity", value: "acme" });
const custom = await client.addEmailDomain({
kind: "custom",
value: "agent.mycompany.com",
delegated: true, // let Seclai manage a dedicated Route53 zone
});
// Publish custom.dns_records at your DNS provider, then check without waiting
// for the background sweep
const checked = await client.verifyEmailDomain(custom.id);
// Promote a verified domain, or fall back to the shared domain
await client.setPrimaryEmailDomain(custom.id);
await client.useSharedEmailDomain(); // keeps the domain configured & verified
// Confirm delivery end-to-end (always sends to the account owner only)
await client.sendEmailDomainTestEmail(custom.id);
// DMARC aggregate-report summary
const dmarc = await client.getDmarcSummary(custom.id, { days: 30, topSources: 10 });
// Removing a delegated domain returns a cleanup_note about the registrar NS record
const { cleanup_note } = await client.removeEmailDomain(custom.id);// Generate plans for different resource types
const kb = await client.aiAssistantKnowledgeBase({ user_input: "Create a docs KB" });
const src = await client.aiAssistantSource({ user_input: "Add a web source" });
const sol = await client.aiAssistantSolution({ user_input: "Set up monitoring" });
const mb = await client.aiAssistantMemoryBank({ user_input: "Create a chat memory" });
// Accept or decline the generated plan
await client.acceptAiAssistantPlan("conversation_id", { confirm_deletions: true });
await client.declineAiAssistantPlan("conversation_id");
// Memory bank conversation history
const history = await client.getAiAssistantMemoryBankHistory();
await client.acceptAiMemoryBankSuggestion("conversation_id", { ... });
// Feedback
await client.submitAiFeedback({ ... });Automatically iterate through all pages of a list method:
for await (const source of client.paginate(
(opts) => client.listSources(opts),
{ limit: 50 },
)) {
console.log(source.name);
}All errors extend SeclaiError:
import {
SeclaiError,
SeclaiConfigurationError,
SeclaiAPIStatusError,
SeclaiAPIValidationError,
SeclaiStreamingError,
} from "@seclai/sdk";
try {
await client.getAgent("bad_id");
} catch (err) {
if (err instanceof SeclaiAPIValidationError) {
console.error("Validation:", err.validationError);
} else if (err instanceof SeclaiAPIStatusError) {
console.error(`HTTP ${err.statusCode}:`, err.responseText);
} else if (err instanceof SeclaiStreamingError) {
console.error("Stream failed for run:", err.runId);
}
}All low-level methods support an AbortSignal for request cancellation:
const controller = new AbortController();
// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5_000);
const data = await client.request("GET", "/agents", {
signal: controller.signal,
});For endpoints not yet covered by a convenience method, use request or requestRaw:
// JSON request/response
const data = await client.request("POST", "/custom/endpoint", {
json: { key: "value" },
query: { filter: "active" },
});
// Raw Response (e.g. binary downloads)
const response = await client.requestRaw("GET", "/files/download/123");
const blob = await response.blob();npm installnpm run typechecknpm run buildThis also regenerates src/openapi.ts from openapi/seclai.openapi.json.
npm testGenerate HTML docs into build/docs/:
npm run docs