Skip to main content

TypeScript SDK

This guide shows how to install the TypeScript SDK (@enterprise-ai/sdk), connect it to the Control Plane of the Navin Platform and call every resource group of API v1 with typed errors, retries, pagination and streaming. Source: sdks/typescript/packages/sdk/src. Every class, method and type is listed in the generated reference.

Version française : typescript.fr.md

Overview​

EnterpriseAI is a promise based client over the standard fetch API. It exposes one sub-client per resource group of API v1 in camelCase (client.agents, client.gateway, client.knowledgeBases, client.finops...). Each method maps one documented route, sends the authentication and tenancy headers, retries the transient failures and throws a typed EaiError subclass. The package also contains ManagerClient for the Navin Manager (Navin Manager SDK), offline contract validation (Contracts v1) and agent helpers (AgentRunner, ContextBudget). Realtime voice lives in the separate package @enterprise-ai/realtime (Realtime guide).

Requirements and compatibility​

ItemSupported
Node.js18 or later ("engines": { "node": ">=18" }), which provides fetch, AbortController and crypto
Other runtimesAny runtime with a standard fetch: browsers, Deno, Bun, edge workers; or pass your own fetch
Module formatsESM (import) and CommonJS (require), with type declarations for both
TypeScriptTypes are shipped in dist/index.d.ts; strict mode is supported
Dependenciesajv and ajv-formats (offline contract validation)
Operating systemsAny OS supported by the runtime (Linux, macOS, Windows)
Control Plane APIv1
warning

In a browser, authenticate with an OIDC token provider, never with an API key: anything shipped to a browser is readable by its user.

Installation​

  1. Download the packages from Harbor. The SDK packages are hosted in the sdk project of the Navinspire Harbor registry (artifact sdk/typescript, tag = SDK version). The Navinspire registry is registry.navinspire.ai; log in with a robot account that can pull from the sdk project (given by the platform team). If your organization mirrors the sdk project in its own Harbor, use the address of that mirror instead.

    oras login registry.navinspire.ai
    oras pull registry.navinspire.ai/sdk/typescript:1.0.2 -o sdk-typescript

    Expected result: the sdk-typescript folder holds enterprise-ai-sdk-1.0.2.tgz and enterprise-ai-realtime-1.0.2.tgz.

  2. Add the package to your project.

    npm install ./sdk-typescript/enterprise-ai-sdk-1.0.2.tgz
    npm install ./sdk-typescript/enterprise-ai-realtime-1.0.2.tgz

    The second line is needed only for voice live sessions. Expected result: package.json lists @enterprise-ai/sdk and node -e "import('@enterprise-ai/sdk').then(m => console.log(typeof m.EnterpriseAI))" prints function.

    If it fails: check node --version (18 or later) and the registry settings of npm (npm config get registry).

Offline install from the tarball​

  1. Copy the tarball pulled from Harbor to the target machine. package.json then holds a file: reference to it; keep the tarball in a folder of the project. The two dependencies of the SDK come from the npm registry configured on the target, or from their own tarballs.

    npm install ./enterprise-ai-sdk-1.0.2.tgz
  2. Or build from this repository.

    cd sdks/typescript && npm ci && npm run build

Quickstart​

This example lists the models the caller may use and sends one chat completion through the gateway, the only path to the models.

import { EnterpriseAI } from "@enterprise-ai/sdk";

const client = new EnterpriseAI({ baseUrl: "<control-plane-url>", apiKey: process.env.EAI_API_KEY, locale: "en" });
const models = await client.models.list({ limit: 20 });
for (const model of models.items) console.log(model.id);
const reply = await client.gateway.chat.completions.create({ model: "gpt-4o-mini", messages: [{ role: "user", content: "Bonjour" }] });
console.log(reply.choices[0]?.message?.content);

What happens: the constructor checks the base URL (https required except on localhost), every call sends the key as X-API-Key and a fresh W3C traceparent, and the completion is posted with an Idempotency-Key. Expected result: the model ids, then the text of the answer. A refused call throws a typed error such as PolicyDeniedError (Errors).

Authentication​

The server always resolves the tenant from the credential. The SDK does not read environment variables: pass the values explicitly, for example from process.env.

MethodOptionHeader sent
Tenant API key eai_<prefix>_<secret> (servers only)apiKeyX-API-Key
OIDC access token, fixed or refreshed by a provider called before each requesttoken: string | () => string | Promise<string>Authorization: Bearer <token>
Development mode (server started with EAI_AUTH_DEV=1)tenant, devUser, devRolesX-Dev-Tenant, X-Dev-User, X-Dev-Roles
Workload identity (SPIFFE, mTLS)none: the mesh authenticates the workloadset by the mesh
import { EnterpriseAI } from "@enterprise-ai/sdk";

const browserClient = new EnterpriseAI({ baseUrl: "<control-plane-url>", token: async () => "<token>", locale: "fr" });
const keys = await browserClient.apiKeys.list();

client.apiKeys.create(...) creates a key (the secret is shown once) and client.apiKeys.rotate(id) rotates it.

Client configuration​

new EnterpriseAI(options: ClientOptions):

NameTypeDefaultRequiredDescription
baseUrlstringnoneyesControl Plane URL without /v1. https required except for localhost, 127.0.0.1 and ::1; embedded credentials are refused.
apiKeystringnonenoTenant API key, sent as X-API-Key.
tokenstring | (() => string | Promise<string>)nonenoOIDC token or provider, sent as Authorization: Bearer.
tenantstringnonenoDevelopment tenant slug, sent as X-Dev-Tenant.
devUserstringnonenoDevelopment user, sent as X-Dev-User.
devRolesstring[]nonenoDevelopment roles, sent as X-Dev-Roles (comma joined).
projectstringnonenoProject slug, sent as X-Project.
environment"dev" | "test" | "staging" | "prod" | stringnonenoEnvironment, sent as X-Environment.
locale"en" | "fr""en"noLanguage of error messages, sent as Accept-Language.
fetch(input: string, init?: RequestInit) => Promise<Response>global fetchnoCustom fetch (proxy agent, tests, instrumentation).
retriesnumber2noRetries after the first attempt (Retries and timeouts).
retryBaseDelayMsnumber300noBase of the exponential backoff, in milliseconds.
retryMaxDelayMsnumber20000noUpper bound of one delay, and of an honored Retry-After.
timeoutMsnumber60000noTimeout of one attempt in milliseconds; 0 disables it. Streams are not timed out.
headersRecord<string, string>{}noExtra headers sent with every request.
sleep(ms: number) => Promise<void>setTimeout basednoSleep between retries, injectable in tests.

Per call options (CallOptions, last argument of every method):

NameTypeDescription
idempotencyKeystringIdempotency-Key of a POST; otherwise one is generated.
traceparentstringW3C trace context of the call; otherwise a random one is generated.
headersRecord<string, string>Extra headers for this call.
signalAbortSignalCancels the call; an aborted call is never retried.
import { EnterpriseAI } from "@enterprise-ai/sdk";

const tuned = new EnterpriseAI({
baseUrl: "<control-plane-url>",
token: async () => "<token>",
project: "invoices",
environment: "prod",
retries: 4,
retryBaseDelayMs: 500,
retryMaxDelayMs: 10000,
timeoutMs: 30000,
headers: { "X-Client": "billing" },
});
const controller = new AbortController();
const agent = await tuned.agents.get("agt_ap", { signal: controller.signal, headers: { "X-Correlation-Id": "c-1" } });

JSON.stringify(client.http) and console.log(client.http) mask the API key and the token. Redirects are never followed (redirect: "manual").

Requests and responses​

Path parameters are positional, the body is one object typed after the v1 contracts, and the result is the decoded JSON body typed by the SDK (Agent, ChatCompletion, Page<T>...). createWithMeta returns the governance headers as well.

const { data: r, meta } = await client.gateway.chat.completions.createWithMeta({ model: "gpt-4o-mini", messages: [{ role: "user", content: "Bonjour" }], eai: { effort: "low", agent_id: "agt_ap" } });
console.log(meta.costMeasured, meta.decisionId);
for await (const chunk of client.gateway.chat.completions.stream({ model: "gpt-4o-mini", messages })) process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
const plan = await client.models.resolve({ model: "o3-mini", effort: "high" });

GatewayMeta holds decisionId (X-EAI-Decision-Id), costEstimated, costMeasured and requestId. The low level client.http.raw(method, template, options) returns { data, status, headers, requestId } for any route.

Errors​

Every error answer uses the envelope {"error": {"code", "message", "message_i18n": {"en", "fr"}, "details", "request_id"}}. The SDK throws the class of the code, else of the HTTP status. Every error has code, status, message (in the client locale), messageI18n, details (secret looking keys masked), requestId and retryAfterMs.

ClassCodesHTTP statusRetried by the SDK
EaiErrorany other code (missing_base_url, no_fetch, invalid_response, http_<status>...)otherno
AuthErrorunauthorized, unauthenticated, forbidden, invalid_credentials401, 403no
PolicyDeniedError (decisionId, reasons)policy_denied403no
QuotaExceededErrorquota_exceeded, budget_exceeded429yes, as a 429, until the retries are spent
EffortNotSupportedErroreffort_not_supported422no
ContextOverflowErrorcontext_overflow422no
ApprovalRequiredError (approvalId)approval_required409no
NotFoundErrornot_found404no
ValidationErrorvalidation_error400, 422no
ConflictErrorconflict409no
PayloadTooLargeErrorpayload_too_large413no
RateLimitErrorrate_limited429yes, then thrown
ServerErrorserver_error and any 5xx500 to 599yes, then thrown
NetworkErrornetwork_error, timeoutnoneyes, then thrown
EgressDeniedError (reason)egress_denied403no
PromptInjectionBlockedErrorprompt_injection_blocked403no
AuthzUnconfirmedErrorraised when an authorization answer has no explicit decisionnoneno
ContractValidationErroroffline contract validation failednoneno
AgentLoopErrorlocal agent loop stoppednoneno
import { QuotaExceededError, EaiError } from "@enterprise-ai/sdk";
try { await client.gateway.chat.completions.create(req); }
catch (e) { if (e instanceof QuotaExceededError) console.log(e.code, e.messageI18n.fr, e.requestId); else throw e; }
for await (const run of client.runs.iterate({ agent_id: "agt_ap" })) console.log(run.run_id);

Retries and timeouts​

An attempt that fails with HTTP 429, any HTTP 5xx, a network error or a timeout is retried up to retries times (default 2), whatever the method: every POST carries an Idempotency-Key, so the server deduplicates the retry. The delay is an exponential backoff with jitter (half of retryBaseDelayMs * 2^attempt plus a random half, at most retryMaxDelayMs); a Retry-After header (seconds or HTTP date) replaces it, bounded by retryMaxDelayMs. The other statuses are thrown at once. timeoutMs (default 60 s) bounds each attempt; an abort through signal is thrown at once.

Pagination​

List routes take limit and cursor and answer Page<T>: { items: T[], next_cursor: string | null }. A list method returns one page; an iterate method (agents, runs, tenants, projects, members, models, approvals, audit) returns an async generator over every item; paginate(fetchPage, params) does the same for any list method and collect(iterable, max) gathers the items in an array.

import { collect, paginate } from "@enterprise-ai/sdk";

const first = await client.runs.list({ agent_id: "agt_ap", limit: 50 });
console.log(first.items.length, first.next_cursor);
const agents = await collect(client.agents.iterate({ limit: 100 }), 500);
const sessions = await collect(paginate((q) => client.sessions.list(q), { limit: 50 }));

paginate stops at the last page or when a cursor repeats.

Idempotency and long operations​

Every POST sends an Idempotency-Key: the idempotencyKey you pass, else a generated UUID kept across the retries of that call. The Control Plane replays the first answer for a key it already saw.

const run = await client.agents.run("agt_ap", { task: { input: { invoice_media_id: "med_01HZX3K9" } } }, { idempotencyKey: "inv-2291" });
const done = await client.runs.get(run.run_id);
console.log(await client.runs.steps(run.run_id));

A long creation answers with the Operation-Id and Operation-Location headers; client.operations.get(id) reads the operation, its tracked resource and its live state. Runs (client.runs.get), sandbox executions (client.sandbox.get) and multimodal jobs (client.multimodal.getJob) are read again until their state is final.

Async and concurrency​

Every method returns a Promise; streams are async generators. One EnterpriseAI instance can serve many concurrent calls (Promise.all): it keeps no per call state. Cancel a call with an AbortSignal.

const [me, models] = await Promise.all([client.me.get(), client.models.list()]);

Streaming and realtime​

client.gateway.chat.completions.stream(body) returns an async generator of ChatCompletionChunk over Server-Sent Events, ending at [DONE]. The lower level client.http.sse(...) and client.http.sseJSON(...) read any SSE route. Voice live sessions use client.realtime.createSession and the @enterprise-ai/realtime package: see the Realtime guide.

Logging and debugging​

The SDK writes no log. To trace the calls, pass a fetch that logs the method, the URL, the status and the X-Request-Id; never log the request headers, which hold the credentials.

import { EnterpriseAI } from "@enterprise-ai/sdk";

const logged = new EnterpriseAI({
baseUrl: "<control-plane-url>",
apiKey: process.env.EAI_API_KEY,
fetch: async (url, init) => {
const res = await fetch(url, init);
console.debug(init?.method ?? "GET", url, res.status, res.headers.get("x-request-id"));
return res;
},
});
console.log(JSON.stringify(logged.http.toJSON()));

Give the requestId of an error to the platform team: it is the key of the audit event and of the trace.

Testing​

Inject a fake fetch: no server is needed and the real SDK code runs (headers, errors, pagination). Set retries: 0 or a no-op sleep to keep the tests fast.

import { EnterpriseAI, NotFoundError } from "@enterprise-ai/sdk";

const fakeFetch = async (url: string): Promise<Response> =>
url.endsWith("/v1/agents/missing")
? new Response(JSON.stringify({ error: { code: "not_found", message: "Resource not found." } }), { status: 404 })
: new Response(JSON.stringify({ items: [{ id: "agt_1" }], next_cursor: null }), { status: 200 });
const testClient = new EnterpriseAI({ baseUrl: "<control-plane-url>", apiKey: "eai_test_key", fetch: fakeFetch, retries: 0 });
const listed = await testClient.agents.list();
try {
await testClient.agents.get("missing");
} catch (err) {
if (!(err instanceof NotFoundError)) throw err;
}

Versioning and compatibility​

  • The package follows semantic versioning: 1.x keeps every exported name; a removal waits for 2.0.
  • It targets API v1 and the contracts v1; unknown response fields are kept, so a newer Control Plane does not break an older SDK.
  • The tests doc-coverage.test.ts and conformance.test.ts check that every route of API v1 is covered by a method.
  • Pin the minor version in production ("@enterprise-ai/sdk": "~1.0.2").

Resource guides​

One subsection per resource group, with one example per common call. Every method and type is in the generated reference.

System, identity and tenancy​

client.meta, client.me, client.catalog, client.operations, client.contracts, client.manifests, client.tenants, client.projects, client.organizations, client.businessUnits, client.members, client.apiKeys, client.serviceIdentities, client.authz, client.secrets.

await client.policies.evaluate({ action: "model.invoke", resource: { type: "model", id: "gpt-4o-mini" } });
await client.approvals.decide("apr_3", { decision: "approve", comment: "ok" });
await client.projects.create({ slug: "invoices", name: "Invoices" });
const res = await client.contracts.validate("usage-event", doc);

Licence, context, delegation and emergency access​

client.license, client.context (execution contexts), client.delegations, client.mandates, client.breakGlass.

const lic = await client.license.get(); // GET /v1/license
await client.license.activate({ token: signedLicense }); // POST /v1/license/activations
const me = await client.me.whoami(); // GET /v1/identity/whoami
const ctx = await client.context.issue({ run_id: "run_1", ttl_seconds: 300 }); // POST /v1/context/issue
await client.context.verify({ token: String(ctx.token) });
const jwks = await client.context.jwks(); // offline verification keys
const d = await client.delegations.create({ delegate: "agent:support", scopes: ["crm.read"], ttl_seconds: 3600 });
await client.delegations.restrict(String(d.id), { delegate: "agent:sub", scopes: ["crm.read"] });
await client.delegations.authorize({ action: "crm.read", resource: "crm:contact:42" });
const m = await client.mandates.create({ agent: "agt_nightly", purpose: "reporting", service_identity_id: "si_1" });
await client.mandates.decide(String(m.approval_id), { decision: "approve" }); // by another person
const grant = await client.breakGlass.request({ reason: "P1 outage", duration_seconds: 900, incident_ref: "INC-12" });
await client.breakGlass.revoke(String(grant.id), { reason: "resolved" });

Policies, approvals, risk and incidents​

client.policies (evaluation, decisions, revisions, simulation, bundles, engines), client.approvals, client.risk, client.incidents, client.governance, client.security.

await client.policies.revisions("pol_1");
await client.policies.diffRevisions("pol_1", { from_revision: 3, to_revision: 4 });
await client.policies.simulate({ candidate: draftPolicy, policy_id: "pol_1" });
await client.policies.createBundle({ revision: "2026-10-30.1" });
const archive = await client.policies.downloadBundle("current"); // ArrayBuffer (bundle.tar.gz)
const engines = await client.policies.engines(); // expected_revision, in_sync

Models, providers, routes and the gateway​

client.providers, client.models, client.routes, client.litellm, client.adapters, client.gateway (chat completions, embeddings, rerank, estimate), client.llm, client.llmManagement, client.isolation.

await client.models.createVersion("gpt-4o-mini", { version: "2026-10" });
await client.models.requestVersionApproval("gpt-4o-mini", "2026-10");
await client.models.startRollout("gpt-4o-mini", "2026-10", { percent: 10 });
await client.models.rollout("gpt-4o-mini");
await client.models.rollback("gpt-4o-mini");
await client.isolation.get({ project_id: "prj_1" }); // GET /v1/llm/isolation

Agents and runs​

client.agents, client.runs, client.checkpoints, client.heartbeats, client.subagents, client.a2a, client.sessions, client.schedules, client.triggers, client.events, client.sandbox.

import { signWebhook, tokenUnits } from "@enterprise-ai/sdk";

const headers = await signWebhook(secret, rawBody, { tenant: "acme", deliveryId: "evt-42" });
await client.webhooks.deliver("sap", { event: "invoice" }, { secret, tenant: "acme" });
const units = tokenUnits(1200, 300, { cached: 800, reasoning: 120 });
const hb = await client.heartbeats.send(runId, { progress: 0.5 });
if (hb.action === "stop") { /* checkpoint and exit */ }

A heartbeat answer with action: "stop" means: save a checkpoint and exit.

Tools, MCP, skills, plugins, connectors and channels​

client.tools, client.mcp, client.mcpRuntime, client.skills, client.plugins, client.connectors, client.credentials, client.webhooks, client.channels, client.navinLibrary, client.fileSecurity.

await client.tools.invoke("erp.lookup", { arguments: { invoice: "INV-2291" }, run_id: run.run_id });
await client.mcp.callTool("mcp_files", "read", { path: "/a.txt" });
await client.skills.verify("invoice-reader");
await client.channels.inbound("web", { user: "u1", text: "Bonjour" });

Data: RAG, memory, DLP and encryption​

client.privacy, client.dlp, client.tokenization, client.encryption, client.knowledgeBases, client.memory, client.retention, client.lineage, client.prompts.

const hits = await client.knowledgeBases.query("kb_1", { query: "délai de paiement", top_k: 5 });
await client.dlp.redact({ text: "IBAN FR76...", mode: "mask" });
const tok = await client.tokenization.tokenize({ values: ["FR7630006000011234567890189"] });

Multimodal​

client.media (upload), client.multimodal (vision, images, video, speech, diarization, audio DLP, jobs), client.realtime (sessions, consents, turns, transcripts).

const media = await client.media.upload({ file, operation: "vision_understanding" });
await client.multimodal.vision({ model: "vision-default", media_id: media.media_id, prompt: "Total ?" });
await client.multimodal.stt({ media_id: "med_AUD1", language: "fr" });
const job = await client.multimodal.generateVideo({ model: "video-gen-gpu", prompt: "Teaser", seconds: 8 });
await client.multimodal.getJob(job.job_id as string);

Quality, audit and observability​

client.evaluations, client.redteam, client.audit, client.telemetry.

const gate = await client.evaluations.checkGate({ target: { type: "agent", ref: "agt_ap", version: "1.4.0" }, suite_ids: ["evs_12"] });
const v = await client.audit.verify();
const corr = await client.audit.correlations({ run_id: "run_42" });
const slo = await client.telemetry.slo({ window_minutes: 60 }); // budget_remaining and burn_rate per service

FinOps and quotas​

client.finops and client.quotas. withReservation reserves, runs your function, then commits on success or releases on error.

const est = await client.finops.estimate({ provider: "openai", usage_type: "llm", model_id: "gpt-4o-mini", units: { input_tokens: 12000, output_tokens: 2000 } });
await client.quotas.withReservation(
{ cost: "0.50", tokens: 4000, scope: { agent: "agt_ap" } },
async (r) => client.gateway.chat.completions.createWithMeta({ model: "gpt-4o-mini", messages }),
{ commitBody: (out) => ({ cost: out.meta.costMeasured, tokens: out.data.usage?.total_tokens }) },
);
const sum = await client.finops.summary({ group_by: "model" });

SOC and kill switches​

client.soc: detections, containment orders, kill switches, receipts, recoveries (four eyes).

await client.soc.createDetection({ kind: "data_exfiltration", severity: "high", agent: "agt_1" });
const order = await client.soc.createOrder({ reaction: "kill_agent", reason: "exfiltration", target: { agent: "agt_1" } });
await client.soc.approveOrder(String(order.id));
await client.soc.verifyKillSwitch("ks_1"); // effective only once every receipt is verified
await client.soc.requestRecovery({ kill_switch_id: "ks_1", reason: "root cause fixed" });
await client.soc.approveRecovery("rec_1"); // four eyes: another person
await client.soc.webhook("falco", rawBody, { headers: { "X-EAI-Signature": signature } }); // raw bytes

Storage, marketplace and evidence​

client.storageProfiles, client.marketplace, client.evidence, client.applications.

const sp = await client.storageProfiles.create({ name: "archive", provider: "s3_compatible", bucket: "eai-archive", credential_ref: "vault:kv/s3" });
await client.storageProfiles.verify(String(sp.id), { probe_write: true });
const { url } = await client.storageProfiles.presign(String(sp.id), { key: "exports/a.json", scope: "read" });

await client.marketplace.preflight({ package: "hr-onboarding", version: "1.2.0", environment: "staging" });
const inst = await client.marketplace.install({ package: "hr-onboarding", version: "1.2.0", environment: "staging" });
await client.marketplace.apply(String(inst.id));

await client.evidence.createSeal();
await client.evidence.verify();
await client.evidence.createHold({ case_id: "case_7", reason: "litigation" });
await client.evidence.runConfiguration("run_1");

Architecture interfaces​

Named methods over documented routes. An OpenFGA or OPA outage propagates as a typed error (ServerError with code authz_backend_error), and an answer without an explicit decision throws AuthzUnconfirmedError: the SDK never reports an allow the engine did not confirm. A refusal propagates as its typed error (AuthError 403 relation_required, ValidationError 422, ConflictError 409 approval_closed or storage_object_protected).

import { AuthError, type AuditRecord } from "@enterprise-ai/sdk";

try {
const ctx = await client.serviceIdentities.assume("si-billing", { purpose: "nightly invoice sync", audience: ["erp"], ttl_seconds: 600 });
console.log(ctx.context_id, ctx.effective.scopes); // verified downstream with POST /v1/context/verify
} catch (err) {
if (err instanceof AuthError && err.code === "relation_required") { /* can_assume not granted */ }
else throw err;
}
const event: AuditRecord = { action: "app.invoice.validated", resource: { type: "invoice", id: "inv-42" }, outcome: "success" };
const receipt = await client.audit.record(event); // receipt.event_id, receipt.hash
await client.approvals.cancel("apr-1", { reason: "request withdrawn" });
const page = await client.storageProfiles.listObjects("sp-1", { prefix: "exports/", limit: 100 });
for (const o of page.items) await client.storageProfiles.deleteObject("sp-1", o.key, o.version_id ? { version_id: o.version_id } : {});

Platform Manager space (platform_admin)​

client.platformManager reaches the Platform Manager space of the console: the Control Plane relays every call to the Navin Manager on behalf of the caller, whose RBAC, approvals and stop rules decide. A refusal comes back with its status and a platform_manager_<code> code.

const summary = await client.platformManager.summary(); // services total, healthy, warning, failed per cluster
const services = await client.platformManager.services("c1"); // five state fields and administrative state
const op = await client.platformManager.requestOperation("c1", "opa", "rolling_restart", { batch_size: 1 });
const restore = await client.platformManager.planRestore({ artifact_id: "b1", environment: "isolated" }); // two approvers other than the requester (ADR-0162)
const exercises = await client.platformManager.drExercises(); // observed RPO and RTO
const links = await client.platformManager.console(); // Manager console and operator consoles

ManagerClient of @enterprise-ai/sdk calls the Navin Manager API directly with a manager token nmt_<12 hex>_<secret>; only GET, PUT and DELETE are retried, because the manager does not deduplicate POST calls. The full guide is Navin Manager SDK.

import { ManagerClient } from "@enterprise-ai/sdk";

const nm = new ManagerClient({ baseUrl: "<manager-url>", token: async () => "<token>", onBehalfOf: "alice" });
const operation = await nm.services.action("c1", "kafka", { action: "restart" });

Reference​