@nexus-legal/sdk
v0.10.0
Published
SDK oficial TypeScript / JavaScript de la API pública v1 de Nexus Legal — análisis legal multijurisdiccional con candados de certeza, jurisprudencia vectorial y sandbox. Wrapper tipado con retries automáticos, idempotency built-in, manejo de rate limits.
Maintainers
Readme
@nexus-legal/sdk
Official TypeScript / JavaScript SDK for the Nexus Legal API v1 — multi-jurisdictional legal analysis with defensible certainty locks, vector case-law search, sandbox, usage analytics and an EU-soil Zero-Retention pipeline.
npm install @nexus-legal/sdkRequires Node.js 18+ (or any runtime with globalThis.fetch).
Quick start
import { NexusClient } from "@nexus-legal/sdk";
const nexus = new NexusClient({ apiKey: process.env.NEXUS_API_KEY! });
const result = await nexus.analyze({
text: "Por el presente contrato, el arrendador cede el uso…",
jurisdiction: "ES",
legalBranch: "civil",
});
console.log(result.analysis);
console.log("Rate limit remaining:", result.rateLimit.remaining);Get an API key at /developers. Full docs at /developers/docs.
Features
- Typed — first-class TypeScript types for every endpoint, request and response.
- Auto idempotency — every
POSTcarries an auto-generatedIdempotency-Key(UUID v4) so retries are safe by default. Opt-out withautoIdempotency: false. - Smart retries — 429 / 5xx with exponential backoff + jitter, honoring
Retry-After. Configurable per request. - Typed errors —
RateLimitError,InsufficientCreditsError,IdempotencyConflictError,ValidationError,AuthenticationError,ServerError,TimeoutError. - Rate-limit telemetry — every response exposes
result.rateLimit.{limit,remaining,reset}. - Zero deps — pure TypeScript, no
node-fetch, nouuid, nojs-yaml. Works in Node, Deno, Bun, and edge runtimes that exposefetch. - Audit-trail parser —
extractAuditTrail()pulls theNEXUS-AUDIT-TRAIL-BEGIN…ENDblock as a structured object. - Multi-agent
mode=deep(v1.6.0+) — Nodo A (Claude Opus 4.7) + Nodo B (DeepSeek V4-Pro) auditor adversarial. Structured findings inresult.audit.
Analysis modes
| Mode | Credits | Pipeline | Use case |
|------|---------|----------|----------|
| standard (default) | 1 | Nodo A only (Claude Opus 4.7) | Most analyses |
| deep | 2 | Nodo A → Nodo B adversarial audit (DeepSeek V4-Pro) | Critical reviews, due diligence, hallucination-sensitive contexts |
Aliases for backward-compat: agil ⇄ standard, auditoria ⇄ deep. The server normalizes them.
const result = await nexus.analyze({
text: contractText,
jurisdiction: "ES",
mode: "deep", // 2 credits — Nodo B auditará el output de Nodo A
});
if (result.audit) {
console.log(`Overall severity: ${result.audit.overallSeverity}`);
for (const finding of result.audit.findings) {
console.log(`[${finding.severity}] ${finding.type}: ${finding.description}`);
}
console.log("Recommendations:", result.audit.recommendations);
}The audit object is present only when mode=deep. Its shape:
interface AuditResult {
auditor: string; // "deepseek-v4-pro"
text: string; // free-form audit prose
findings: AuditFinding[]; // structured list
overallSeverity: "low" | "medium" | "high";
recommendations: string;
processingMs: number;
}
interface AuditFinding {
type: "hallucination" | "missing_clause" | "weak_reasoning"
| "citation_error" | "other";
severity: "low" | "medium" | "high";
description: string;
}API surface
const nexus = new NexusClient({
apiKey: "nlk_…",
baseUrl: "https://nexusquantum.legal", // default
timeout: 120_000, // ms, default 2 min
maxRetries: 3, // default
autoIdempotency: true, // default
});
// Top-level
await nexus.analyze({ text, jurisdiction, legalBranch, language, mode });
// Sandbox (no credits consumed)
await nexus.sandbox.analyze({ sample: "providencia_aeat" });
await nexus.sandbox.metadata();
// Case-law search
await nexus.jurisprudencia.search({ query, jurisdiction, topK });
// Vigent legislation (P0 — rails-only until activation per contract)
await nexus.normativa.articulo({ // literal a fecha
norma: "BOE-A-1889-4763",
articulo: "1124",
fecha: "2026-05-21",
jurisdiccion: "ES",
});
await nexus.normativa.search({ // búsqueda semántica
query: "responsabilidad contractual del arrendatario",
jurisdiccion: "ES",
topK: 10,
});
await nexus.normativa.getVersiones("BOE-A-1889-4763");
await nexus.normativa.coverage(); // status + totals, no auth
// Catalogs and usage
await nexus.jurisdictions.list();
await nexus.usage.get({ from, to, granularity });
await nexus.usage.csv({ from, to, granularity });
// Corpus coverage (auto-discovered, includes all indexed tables)
await nexus.corpus.coverage();
// White-label config (partner/distributor — v1.7.0+)
await nexus.branding.get();
await nexus.branding.update({
branding: "partner",
partner_brand_name: "Lemontech AI",
partner_legal_name: "Lemontech S.A.",
partner_logo_url: "https://cdn.lemontech.com/logo.png",
partner_primary_color: "#1A5F8B",
partner_support_email: "[email protected]",
partner_website: "https://lemontech.com",
});
// Si la key es master de una org, los cambios se propagan a todas las
// sub-keys automáticamente. Sub-keys (child) reciben 403.
// Webhooks (Tier 2)
await nexus.webhooks.create({
url: "https://your-server.com/webhook",
events: ["analysis.completed", "analysis.failed", "credits.low"],
});
await nexus.webhooks.list();
await nexus.webhooks.update(id, { active: false });
await nexus.webhooks.delete(id);
// v0.7.0+ — inspect deliveries (debug)
const { deliveries } = await nexus.webhooks.getDeliveries(webhookId, {
limit: 100,
status: "failed", // pending | success | failed | exhausted
});
for (const d of deliveries) {
console.log(d.event, d.status, d.last_status_code, d.attempts);
}
// DSR export (GDPR Art.15 + Art.20 — v0.6.0+)
const bundle = await nexus.account.exportDsr();
// Para masters de organización:
// const subBundle = await nexus.account.exportDsr({ forSubKey: subKeyId });
// Organizations + sub-keys (Tier 2)
await nexus.organizations.create({ name: "Despacho Vázquez" });
await nexus.organizations.list();
const subKey = await nexus.organizations.createKey(orgId, {
childLabel: "Cliente Acme",
scopes: ["analyze", "query"],
});
// subKey.key se muestra UNA SOLA VEZ — guárdalo seguroError handling
import {
InsufficientCreditsError,
RateLimitError,
ValidationError,
} from "@nexus-legal/sdk";
try {
await nexus.analyze({ text, jurisdiction: "ES" });
} catch (err) {
if (err instanceof InsufficientCreditsError) {
console.error(`Need ${err.required} credits, have ${err.balance}.`);
} else if (err instanceof RateLimitError) {
console.warn(`Rate limited. Retry in ${err.retryAfter}s.`);
} else if (err instanceof ValidationError) {
console.error(`Bad request: ${err.message}`);
// err.code is a stable identifier (VALIDATION_ERROR, BATCH_TOO_LARGE, ...)
console.error(`Code: ${err.code}, request_id: ${err.requestId}`);
} else {
throw err;
}
}Error format (v1.6.0+) — stable, Stripe-style:
{
"error": {
"type": "invalid_request_error",
"code": "VALIDATION_ERROR",
"message": "Texto demasiado corto (mín. 50 chars).",
"request_id": "req_abc-123",
"details": { "field": "text", "min": 50, "got": 12 }
},
"error_message": "Texto demasiado corto (mín. 50 chars).", // backward-compat alias
"code": "VALIDATION_ERROR" // backward-compat alias
}Every response (success or error) includes header X-Request-Id: req_<uuid>. The SDK surfaces it as err.requestId — reference it in support tickets. You can also pre-set your own via X-Request-Id header (validated regex [A-Za-z0-9_-]{8,100}, ignored otherwise).
Error types: invalid_request_error, authentication_error, permission_error, rate_limit_error, insufficient_credits_error, not_found_error, conflict_error, api_error.
Idempotency
Every POST is sent with a fresh Idempotency-Key. If the network fails between the request and the response, the SDK retries with the same key — Nexus returns the cached result (24-hour TTL) without re-charging credits. The flag result.idempotencyReplayed tells you whether the response came from the cache.
To use your own key (e.g. derived from a domain identifier so multiple processes converge):
await nexus.analyze(
{ text, jurisdiction: "ES" },
{ idempotencyKey: `expediente-${expedienteId}` },
);Reusing the same key with a different body throws IdempotencyConflictError (HTTP 409).
Retries
By default the SDK retries 429 / 5xx up to 3 times with exponential backoff (min(2^attempt, 30s) + jitter). Override per client or per call:
const nexus = new NexusClient({ apiKey, maxRetries: 5 });
// or per request
await nexus.analyze({ text, jurisdiction: "ES" }, { maxRetries: 0 });Network failures and TimeoutErrors are retried with the same policy.
Audit-trail parsing
import { extractAuditTrail } from "@nexus-legal/sdk";
const result = await nexus.analyze({ text, jurisdiction: "ES" });
const trail = extractAuditTrail(result.analysis);
if (trail?.levels_emitted["L5-C"] && trail.levels_emitted["L5-C"] > 0) {
// Critical contractual risk — escalate to human reviewer.
}
for (const flag of trail?.review_flags ?? []) {
await createTask({ title: flag });
}The parser is intentionally narrow (no YAML dependency). For arbitrary YAML, use the raw field with js-yaml.
Cancellation and timeouts
const ctrl = new AbortController();
setTimeout(() => ctrl.abort(), 5_000);
await nexus.analyze({ text, jurisdiction: "ES" }, { signal: ctrl.signal });Per-call timeouts override the client-level default:
await nexus.analyze({ text, jurisdiction: "ES" }, { timeout: 60_000 });TimeoutError is retried like network errors.
Compatibility
| Runtime | Status |
| -------------------- | ---------------- |
| Node.js ≥ 18 | ✅ first-class |
| Bun | ✅ |
| Deno | ✅ (via npm:) |
| Cloudflare Workers | ✅ |
| Vercel Edge runtime | ✅ |
| Browser | ⚠️ technically works, but never ship an API key to the browser — proxy through your backend |
Versioning
This SDK targets API v1.x and follows SemVer:
- MAJOR — drops support for an API breaking change (we mirror Nexus's 6-month deprecation window).
- MINOR — new endpoints, new fields, new options.
- PATCH — bug fixes, doc improvements.
See the API changelog.
License
MIT — see LICENSE.
Built by Nexus Legal. Questions? Email [email protected].
