Build secure AI apps faster with Terso.
A production-oriented TypeScript client for submitting and monitoring security scans through the Terso API, with privacy-preserving finding proofs anchored on Solana.
Security-first
Keys and private findings stay protected.
Typed API
ESM, CommonJS, and first-class TypeScript.
Proof-ready
Anchor deterministic hashes on Solana.
Installation
Install the package from npm. Terso requires Node.js 18 or newer, or a modern runtime with standards-compatible fetch, AbortController, and Web APIs.
npm i @labsbtd/tersoBoth ESM and CommonJS entry points are included. TypeScript is recommended but optional for consumers.
Quick start
Create a client, submit a repository for scanning, and wait for the scan to reach a terminal state.
import { Terso } from "@labsbtd/terso";
const terso = new Terso({
apiKey: process.env.TERSO_API_KEY!,
baseUrl: process.env.TERSO_BASE_URL,
});
const scan = await terso.scans.create({
projectName: "my-app",
source: {
type: "repository",
url: "https://github.com/example/my-app",
branch: "main",
},
});
const result = await terso.scans.waitForResult(scan.id);Provide credentials through your application environment. The SDK deliberately does not load dotenv.
TERSO_API_KEY=terso_your_api_key_here
TERSO_BASE_URL=https://api.terso.example/v1Configuration
new Terso(options) accepts authentication, transport, retry, and Solana proof settings.
| Option | Type / Default | Description |
|---|---|---|
| apiKey | string · required | Bearer credential. Never included in SDK-generated errors. |
| baseUrl | string · API default | API base URL. Trailing slashes are removed. |
| timeoutMs | number · 30000 | Timeout for each individual HTTP attempt. |
| maxRetries | number · 2 | Additional attempts for retryable failures. |
| fetch | global fetch | Inject a compatible fetch implementation. |
| userAgentSuffix | string · none | Append an application identifier to the user agent. |
| solana | SolanaProofOptions · devnet | Cluster, RPC endpoint, and commitment for proofs. |
Security scans
Create scans from a repository, remotely hosted archive, or inline code. Optional metadata lets you associate the scan with an environment or workflow.
await terso.scans.create({
projectName: "service-api",
source: { type: "archive", url: "https://downloads.example/service.tgz" },
metadata: { environment: "staging" },
});
await terso.scans.create({
projectName: "snippet",
source: { type: "code", content: "const value = input;", filename: "index.ts" },
});Retrieve and manage scans
const scan = await terso.scans.get("scan_123");
const page = await terso.scans.list({ page: 1, pageSize: 25 });
const cursorPage = await terso.scans.list({ cursor: "next_cursor" });
const cancelled = await terso.scans.cancel("scan_123");Polling and cancellation
Wait for completed, failed, or cancelled. Polling cleans up its timers when complete, timed out, or aborted.
const controller = new AbortController();
setTimeout(() => controller.abort(), 10_000);
await terso.scans.waitForResult("scan_123", {
pollingIntervalMs: 2_000,
timeoutMs: 5 * 60_000,
signal: controller.signal,
});Privacy-preserving Solana proofs
Terso hashes a finding locally and records only its deterministic SHA-256 hash through Solana's Memo program. Finding titles, descriptions, files, remediation, and report details never enter the transaction.
import { Keypair } from "@solana/web3.js";
import { Terso, type Finding } from "@labsbtd/terso";
const terso = new Terso({
apiKey: process.env.TERSO_API_KEY!,
solana: { cluster: "devnet" },
});
const finding: Finding = {
id: "finding_123",
title: "SQL injection",
description: "Private evidence retained by the report owner",
severity: "high",
};
const proof = await terso.proofs.create({
finding,
signer: Keypair.generate(),
});
const result = await terso.proofs.verify({
finding,
transactionSignature: proof.transactionSignature,
});terso:v1: marker are public—but the sensitive finding content stays off-chain.Deterministic verification
proofs.hash(finding) produces the same lowercase digest without a network request. Keys are recursively sorted and undefined properties are omitted. Changing any included value changes the hash.
proofs.verify(input) fetches the transaction, locates the Terso Memo, recomputes the hash locally, checks transaction success, and reports whether the values match. Verification proves content integrity—not whether the finding itself is objectively correct.
| Setting | Supported values | Default |
|---|---|---|
| cluster | devnet · testnet · mainnet-beta · custom | devnet |
| rpcUrl | Valid Solana RPC URL | Required for custom |
| commitment | processed · confirmed · finalized | confirmed |
Errors
Every SDK error extends TersoError, so applications can handle authentication, rate limits, API responses, network failures, validation, and timeouts precisely.
try {
await terso.scans.get("scan_123");
} catch (error) {
if (error instanceof TersoRateLimitError) {
console.error(error.requestId, error.retryAfterMs);
} else if (error instanceof TersoAuthenticationError) {
console.error("Check server-side Terso credentials");
} else if (error instanceof TersoApiError) {
console.error(error.statusCode, error.code, error.requestId);
} else if (error instanceof TersoTimeoutError) {
console.error("The operation timed out");
}
}| Error | Meaning |
|---|---|
| TersoApiError | API error with statusCode, code, requestId, and optional details. |
| TersoAuthenticationError | HTTP 401 or 403 authentication failure. |
| TersoRateLimitError | HTTP 429 with optional retryAfterMs. |
| TersoTimeoutError | A request or polling deadline was exceeded. |
| TersoNetworkError | A transport or connectivity failure. |
| TersoValidationError | Invalid client input before the request. |
Retry and timeout behavior
The client retries network errors and HTTP 408, 429, 500, 502, 503, and 504 responses up to maxRetries. Backoff is capped, exponential, jittered, and honors numeric or HTTP-date Retry-After values.
timeoutMs applies to each HTTP attempt; waitForResult has its own total timeout.API and types
| Export | Description |
|---|---|
| Terso | Main client exposing scans, health, and proofs APIs. |
| TersoOptions | Client constructor configuration. |
| CreateScanInput | Project, source, and optional scan metadata. |
| ScanSource | Repository, archive, or inline code source. |
| Scan / ScanStatus | Scan state, lifecycle status, and completed result. |
| Finding / FindingSeverity | Finding data and critical, high, medium, low, or info severity. |
| PaginationInput | Page-based or cursor-based list options. |
| WaitForResultOptions | Polling interval, timeout, and abort signal. |
| SolanaProofOptions | Cluster, RPC endpoint, and commitment settings. |
| SolanaProof | On-chain proof transaction receipt. |
| ProofVerification | Verification result and matched proof details. |
| VERSION | Current SDK version: 1.1.4. |
Public methods include scans.create, scans.get, scans.list, scans.cancel, scans.waitForResult, health.check, proofs.hash, proofs.create, and proofs.verify.
Assumed REST contract
The transport centralizes the documented API assumptions, allowing the base URL to change without changing resource code.
| Method | Endpoint | Description |
|---|---|---|
| POST | /scans | Create a scan and return Scan. |
| GET | /scans/{scanId} | Retrieve a scan by encoded ID. |
| GET | /scans?page=&pageSize=&cursor= | List scans with pagination. |
| POST | /scans/{scanId}/cancel | Cancel a scan and return its updated state. |
| GET | /health | Return service status and optional version. |
Requests use bearer authentication, JSON content negotiation, X-Terso-SDK-Version, and a user agent. Structured errors are expected as error: { code?, message, details? } with an optional request ID.
Development and publishing
Run the full local verification pipeline before publishing. prepublishOnly performs package validation, but publishing happens only when explicitly requested.
npm install
npm run typecheck
npm test
npm run test:coverage
npm run lint
npm run format:check
npm run build
npm run validate:package
npm pack