TypeScript SDK
The @armoriq/sdk package for Node.js agents. ArmorIQClient, capturePlan / getIntentToken / invoke loop, per-call user scoping, and known parity gaps with the Python SDK.
The TypeScript SDK ships as @armoriq/sdk on npm. Use it from any Node.js 18+ runtime — Express servers, serverless functions, Deno, Bun.
npm install @armoriq/sdk
# or pnpm add / yarn addThere is no TypeScript CLI. Use the Python CLI (pipx install armoriq-sdk) for armoriq login, armoriq orgs, and armoriq switch-org. Both SDKs read the same ~/.armoriq/credentials.json — a single login works for both runtimes.
Minimum setup
import { ArmorIQClient } from '@armoriq/sdk';
const client = new ArmorIQClient({
apiKey: process.env.ARMORIQ_API_KEY!,
userId: 'my-service',
agentId: 'my-agent',
});Three required options:
apiKey— your ArmorIQ API key, typically from an env var. Falls back toARMORIQ_API_KEYand then the credentials file if omitted.userId— an identifier for the service or worker. Auto-default is not applied on TS (unlike Python).agentId— an identifier for the agent persona. Also required.
All other options (endpoints, proxy mapping, timeouts) auto-resolve to platform defaults.
Full constructor
new ArmorIQClient({
apiKey: string, // required
userId: string, // required
agentId: string, // required
contextId?: string, // default: 'default'
iapEndpoint?: string, // auto-resolves
proxyEndpoint?: string,
backendEndpoint?: string,
proxyEndpoints?: Record<string, string>, // per-MCP overrides
timeout?: number, // ms, default 30000
verifySsl?: boolean, // default true
useProduction?: boolean, // default true
mcpCredentials?: Record<string, any>,
})The plan → token → invoke loop
Same three-step flow as Python. All network operations are async.
capturePlan(llm, prompt, plan, metadata?)
Capture the LLM's planned tool calls. The plan object is required — it must contain a steps array:
const plan = client.capturePlan(
'gpt-4o',
"What's the weather in Paris?",
{
goal: 'Weather lookup',
steps: [
{
action: 'get_weather',
mcp: 'weather-mcp',
params: { city: 'Paris', units: 'celsius' },
},
],
},
);
console.log(plan.planHash);
console.log(plan.merkleRoot);getIntentToken(plan, options?)
Mint a short-lived signed token:
const token = await client.getIntentToken(plan, { validitySeconds: 300 });
console.log(token.tokenId);
console.log(token.expiresAt);invoke(mcp, action, token, params?, merkleProof?, userEmail?)
Execute the tool call through the proxy:
const result = await client.invoke(
'weather-mcp',
'get_weather',
token,
{ city: 'Paris', units: 'celsius' },
undefined, // merkleProof — optional
'alice@customer.com', // userEmail — the end user on whose behalf
);
console.log(result.status);
console.log(result.data);invokeWithPolicy(mcp, action, token, params, options?)
The richer variant. Takes options for user email, hold-approval polling, and delegation:
const result = await client.invokeWithPolicy(
'weather-mcp',
'get_weather',
token,
{ city: 'Paris' },
{
userEmail: 'alice@customer.com',
waitForApproval: true, // poll on PolicyHold
holdPollIntervalMs: 3000,
holdPollAttempts: 20, // 60s total
},
);Use this when the tool call might require human approval — the SDK handles delegation creation and approval-polling automatically.
Email scoping
TypeScript passes the end-user email per call, on invoke() or in invokeWithPolicy options. Every tool call tagged with userEmail appears in audit logs under that user, and policies scoped per-email / per-membership are evaluated against them.
// Per-user invocations
for (const email of ['alice@acme.com', 'bob@acme.com']) {
const plan = client.capturePlan(llm, prompt, planDefinition);
const token = await client.getIntentToken(plan);
await client.invoke('weather-mcp', 'get_weather', token, {...}, undefined, email);
}Exception handling
All exceptions extend ArmorIQException. Catch broadly or specifically.
import {
InvalidTokenException,
TokenExpiredException,
IntentMismatchException,
PolicyBlockedException,
PolicyHoldException,
MCPInvocationException,
ConfigurationException,
} from '@armoriq/sdk';
try {
const result = await client.invoke('mcp', 'action', token, params);
} catch (err) {
if (err instanceof TokenExpiredException) {
const fresh = await client.getIntentToken(plan);
// retry...
} else if (err instanceof PolicyBlockedException) {
console.error(`Blocked: ${err.message}`);
} else if (err instanceof PolicyHoldException) {
console.error(`Approval required: delegation=${err.delegationId}`);
}
}| Exception | You'll see it when… | What to do |
|---|---|---|
ConfigurationException | No API key, or missing userId/agentId | Set env vars, or pass them to the constructor |
InvalidTokenException | Signature mismatch | Capture a fresh plan + token |
TokenExpiredException | Token past validitySeconds | Call getIntentToken again |
IntentMismatchException | invoke args don't match plan.steps | Align params, or capture a new plan |
PolicyBlockedException | Tool not in user's allow-list | Update policy in the dashboard |
PolicyHoldException | Approval required | Use invokeWithPolicy({ waitForApproval: true }) |
MCPInvocationException | MCP server itself errored | Inspect MCP logs; not an ArmorIQ issue |
Known gaps vs the Python SDK
The TypeScript SDK is feature-complete for core use cases but has a few parity gaps. All are documented here so you're not surprised.
| Feature | Python | TypeScript |
|---|---|---|
ArmorIQClient (core) | ✓ | ✓ |
capturePlan / getIntentToken / invoke | ✓ | ✓ |
invokeWithPolicy (with hold auto-retry) | ✓ | ✓ |
Per-call userEmail on invoke | ✓ | ✓ |
for_user(email) scope wrapper | ✓ | not yet |
from_config("armoriq.yaml") | ✓ | not yet |
Session mode (ArmorIQSession) | ✓ | not yet |
CLI (armoriq login etc.) | ✓ | not shipped — use Python CLI |
| Google ADK integration | ✓ | Python only |
For any gap on the TS side, use one of these workarounds:
for_user(email)/ session mode → passuserEmailper-call oninvoke/invokeWithPolicy.from_config→ parse the YAML manually withyamlorjs-yaml, then pass fields to the constructor.- CLI → install and use the Python CLI; the TS runtime reads the same credentials file.
Full worked example
import { ArmorIQClient, TokenExpiredException } from '@armoriq/sdk';
const client = new ArmorIQClient({
apiKey: process.env.ARMORIQ_API_KEY!,
userId: 'weather-service',
agentId: 'weather-bot',
});
async function getWeather(userEmail: string, city: string) {
const plan = client.capturePlan(
'gpt-4o',
`What's the weather in ${city}?`,
{
goal: 'Weather lookup',
steps: [
{
action: 'get_weather',
mcp: 'weather-mcp',
params: { city, units: 'celsius' },
},
],
},
);
let token = await client.getIntentToken(plan, { validitySeconds: 300 });
try {
const result = await client.invoke(
'weather-mcp',
'get_weather',
token,
{ city, units: 'celsius' },
undefined,
userEmail,
);
return result.data;
} catch (err) {
if (err instanceof TokenExpiredException) {
token = await client.getIntentToken(plan);
const result = await client.invoke(
'weather-mcp',
'get_weather',
token,
{ city, units: 'celsius' },
undefined,
userEmail,
);
return result.data;
}
throw err;
}
}
// Usage
const data = await getWeather('alice@customer.com', 'Paris');
console.log(data);Next steps
Python SDK
The armoriq_sdk package for Python agents. ArmorIQClient, for_user, from_config, sessions, exception handling, and the plan-token-invoke loop.
armoriq.yaml Reference
Complete schema for the armoriq.yaml config file — identity, proxy, MCP servers, policy, intent settings, and $ENV_VAR substitution.