Install and use @contro1/sdk in Node, TypeScript, Express, Fastify, or Next.js backends to create approval requests, verify webhooks, and write audit records.
Use @contro1/sdk when your agent backend, tool runner, webhook bridge, or application server runs on Node or TypeScript.
Use the integration skill
Copy this skill link into your code agent to add JavaScript / TypeScript SDK and Contro1 to your system.
import { CentcomClient } from '@contro1/sdk';
export const centcom = new CentcomClient({
apiKey: process.env.CENTCOM_API_KEY!,
baseUrl: process.env.CENTCOM_BASE_URL,
});
Run without an API key
An API key is the quickest start, but a long-lived secret in an environment variable is also the easiest thing for an agent, a log or a leaked image to expose. For agents that run unattended, use an owner-approved connection instead. The client takes exactly one identity: apiKey, tokenProvider or transport.
On a computer connected with contro1 connect, pass transport: brokerTransport(endpoint). The Contro1 service holds the key and your process holds no credential. Works with Unix sockets and Windows named pipes.
In the cloud (GitHub Actions, Kubernetes, any OIDC issuer), exchange the platform's own identity token with exchangeWorkloadToken. An administrator writes a trust policy and the agent's owner approves it; nothing your code sends decides which agent it becomes.
RuntimeTokenProvider refreshes on its own, never runs two refreshes at once, and throws RuntimeCredentialError with a remediation that says who can fix a revoked or expired connection.
contro1Runtime.ts
import { CentcomClient, brokerTransport, exchangeWorkloadToken, generateDpopKey } from '@contro1/sdk';
// On a connected computer: the local Contro1 service holds the credential
export const centcom = new CentcomClient({
transport: brokerTransport(process.env.CONTRO1_BROKER_ENDPOINT!),
});
// In CI or Kubernetes: trade the platform's OIDC token for a 5-minute Contro1 token
const tokens = await exchangeWorkloadToken({
apiUrl: 'https://api.contro1.com',
subjectToken: process.env.OIDC_TOKEN!,
key: generateDpopKey(),
});
Say who can instruct this agent
Contro1 refuses a personal account to an agent that more than one person can instruct. It has to: the agent performs the action with its own authority, so on a shared surface it cannot tell its owner from anybody else who reached it the same way. Put an assistant that reads its owner's mailbox behind an API your customers call, and any of them can ask it what the mailbox holds.
On a platform Contro1 connects to, an adapter reports this. An agent you built with the SDK has no adapter to ask: a nightly job only you trigger and a service answering thousands of customers look identical from here. So you say which it is.
The declaration can only ever make this agent stricter. It arrives on the agent's own credential, which means it is the software describing itself, and letting it claim to be private would let any agent unlock personal accounts by saying so. The server refuses that shape by name. Privacy is established by a person, with contro1 connect, on a machine they control.
Nothing changes for an agent that does not declare it, and nothing changes for organization or shared connections, which already sit inside a resource boundary somebody approved. This is specifically about one person's account being borrowed by software that answers to several.
client.ts
import { CentcomClient } from '@contro1/sdk';
const client = new CentcomClient({
apiKey: process.env.CENTCOM_API_KEY,
// Sent once, before the first call. Only ever makes this agent stricter.
reach: {
contexts: [
{
context_id: 'support-api',
label: 'Customer support API',
kind: 'shared',
participants_known: false,
},
],
},
});
Create an approval request
Create the request before the risky tool or workflow step executes. Use external_request_id for retries and correlation_id to group related requests and audit records.
Build context at the gate: copy the exact tool input your code intercepted (machine-observed), attach what triggered the run, and carry the agent's justification by making reason a required parameter of the risky tool. Label model-written text as agent-reported - it informs the reviewer but never changes routing or risk_level.
approvalRequest.ts
// "reason" is a required tool parameter: the model justifies at decision time
async function deploy(service: string, version: string, reason: string) {
const request = await centcom.createProtocolRequest({
title: 'Approve production deploy?',
request_type: 'approval',
source: { integration: 'deploy-agent', workflow_id: 'release', run_id },
routing: { required_role: 'release_manager', priority: 'urgent', sla_minutes: 10 },
context: {
action_type: 'deploy',
resource: service,
tool_input: { service, version },
summary: 'Deploy release with billing migration to production.',
agent_reported: { justification: reason },
},
risk_level: 'high',
policy_trigger: 'Production deploys with billing migrations require release approval.',
approval_comment_required: true,
continuation: { mode: 'decision', webhook_url: 'https://app.example.com/api/contro1/webhook' },
external_request_id: `deploy:${run_id}:billing-api`,
correlation_id: `case_deploy_${run_id}`,
});
return request;
}
client.asSubAgent(name) is the same agent acting as one named part of it: every call from that view carries the Contro1-Sub-Agent header, and Contro1 registers the part under this agent the first time it is seen. A part runs on this agent's grants and never more.
TraceRun reports a run step by step under one trace_id, from the code around the model. run.wrap(name, fn) reports every call; run.subAgent(name) is a part, as a child run. With failClosed a tool whose start could not be recorded does not run.
Beyond the approval call, attach a run trace, the tools you invoked, and the context you retrieved. Each field is optional; add what you have. Identity always comes from your credential. When a named part of a multi-agent system made the call, say which with the Contro1-Sub-Agent header (as_sub_agent in the SDKs): Contro1 registers it under this agent, and it never has more authority than this agent.
trace_id / parent_trace_id - link one run (and sub-agent runs) into a single trace.
tool_calls[] - what the agent tried to do, so reviewers see the actions. A hook can report every tool call for you.
retrieved_context[] - the data the decision was based on (RAG provenance).
Then export a signed evidence packet from GET /requests/:id/evidence.
Send full traceability
// Same approval call you already make - now with full traceability.
const billing = client.asSubAgent("billing"); // optional: a named part of this agent
await billing.createProtocolRequest({
request_type: "approval",
title: "Refund $4,200 to customer 8831",
source: { integration: "api" },
trace_id: `trc_${runId}`, // link every step of this run
tool_calls: [{ name: "lookup_order", outcome: "success" }],
retrieved_context: [{ source: "policy:refunds", uri: "kb://policy/refunds" }],
continuation: { mode: "decision" },
});
The signed webhook is cryptographic proof of a human decision. Verify it inside the system that executes the action - not inside the agent. Any tool that must never run without human sign-off (payments, deploys, data deletion) should refuse to act without a verified approval; that way no agent, including shadow agents nobody registered, can trigger it by skipping Contro1.
Verify the signature and reject timestamps older than 5 minutes (replay protection; the timestamp marks callback delivery, not request creation, so long SLAs are unaffected).
Bind the approval to the exact action parameters via metadata / correlation_id - never treat "an approval arrived" as permission for a different action.
Execute each request_id exactly once (idempotency on your side).
When in doubt, confirm state directly with GET /v1/requests/:id using a read-only API key.
No. On a computer connected with contro1 connect, use brokerTransport so the local Contro1 service holds the credential. In CI or Kubernetes, use exchangeWorkloadToken under an owner-approved trust policy. Both avoid a long-lived secret, and the client refuses to be configured with more than one identity.
Is this a Java SDK?
No. The current public repo is JavaScript/TypeScript for Node runtimes. A Java SDK would need a separate package and repo.
Should I use webhooks or polling?
Use webhooks in production when possible. Polling is useful for local tools, jobs without inbound HTTP, or early prototypes.
Install and use the framework-agnostic Contro1 Python SDK to request human approvals or input, verify signed webhooks, log autonomous actions, and fetch audit evidence.