AI Agent Auth
AI Agent Auth is the direct runtime lane for agent apps, desktop harnesses, and tool providers that need scoped user-approved delegation without handing provider API keys to the agent runtime.
Pair with a runtime-generated QR
Direct runtimes generate a key-bound QR locally. The user scans that QR in Nuvouch mobile, receives a one-time code, and gives the code back to the runtime. The runtime claims the connection by signing the claim payload with the same private key that created the QR.
import {
buildAgentAuthRuntimeClaimPayload,
createAgentAuthRuntimeClient,
createAgentAuthRuntimePairingRequest,
signAgentAuthRuntimePayload,
} from "@nuvouch/agent-auth";
const runtime = createAgentAuthRuntimeClient();
const pairing = createAgentAuthRuntimePairingRequest({
applicationId: "app_openclaw",
runtimeDisplayName: "OpenClaw Desktop",
deviceName: "Ada's MacBook",
});
// Render pairing.qrData as a QR code.
// The user scans it in Nuvouch mobile and enters the generated code.
const code = "NVP1-...";
const claimPayload = buildAgentAuthRuntimeClaimPayload({ code });
const connection = await runtime.claimConnection({
code,
publicKeyFingerprint: pairing.publicKeyFingerprint,
signature: signAgentAuthRuntimePayload({
privateKey: pairing.keyPair.privateKey,
keyAlgorithm: pairing.keyPair.keyAlgorithm,
payload: claimPayload,
}),
});
console.log(connection.id, connection.status);Request, wait for, and revoke delegation
import {
buildAgentAuthDelegationReadPayload,
buildAgentAuthDelegationRequestPayload,
createAgentAuthNonce,
signAgentAuthRuntimePayload,
} from "@nuvouch/agent-auth";
const nonce = createAgentAuthNonce("delreq");
const requestPayload = buildAgentAuthDelegationRequestPayload({
runtimeConnectionId: connection.id,
audienceId: "aud_test_payments",
agentId: "agent_billing",
purpose: "Identify the user before preparing a payment approval.",
scopes: ["payments.identity", "payments.prepare"],
nonce,
});
const delegation = await runtime.requestDelegation({
runtimeConnectionId: connection.id,
audienceId: "aud_test_payments",
agent: {
id: "agent_billing",
name: "Billing Agent",
},
purpose: "Identify the user before preparing a payment approval.",
scopes: ["payments.identity", "payments.prepare"],
nonce,
signature: signAgentAuthRuntimePayload({
privateKey: pairing.keyPair.privateKey,
keyAlgorithm: pairing.keyPair.keyAlgorithm,
payload: requestPayload,
}),
});
const approved = await runtime.waitForDelegation({
runtimeConnectionId: connection.id,
delegationId: delegation.id,
sign: ({ nonce }) => {
const payload = buildAgentAuthDelegationReadPayload({
runtimeConnectionId: connection.id,
delegationId: delegation.id,
nonce,
});
return signAgentAuthRuntimePayload({
privateKey: pairing.keyPair.privateKey,
keyAlgorithm: pairing.keyPair.keyAlgorithm,
payload,
});
},
});
if (!approved.token) {
throw new Error(`Delegation was ${approved.status}`);
}Revoke the runtime connection
import {
buildAgentAuthRuntimeConnectionRevokePayload,
createAgentAuthNonce,
signAgentAuthRuntimePayload,
} from "@nuvouch/agent-auth";
const revokeNonce = createAgentAuthNonce("revoke");
const revokePayload = buildAgentAuthRuntimeConnectionRevokePayload({
runtimeConnectionId: connection.id,
nonce: revokeNonce,
});
await runtime.revokeConnection({
runtimeConnectionId: connection.id,
nonce: revokeNonce,
signature: signAgentAuthRuntimePayload({
privateKey: pairing.keyPair.privateKey,
keyAlgorithm: pairing.keyPair.keyAlgorithm,
payload: revokePayload,
}),
reason: "User signed out of runtime",
});Introspect tokens before using a tool
Providers use their own Nuvouch API key to introspect delegation tokens. Accept only active tokens with the expected audience and scopes, then map the returned scoped subject to a local account.
import { Nuvouch } from "@nuvouch/node";
const nuvouch = new Nuvouch({
apiKey: process.env.NUVOUCH_PROVIDER_API_KEY,
});
const delegation = await nuvouch.agentAuth.provider.requireDelegation({
token: req.headers.authorization?.replace(/^Bearer\s+/i, "") ?? "",
audienceKey: "stripe:payments",
requiredScopes: ["payments.prepare"],
});
const localAccount = await mapScopedSubjectToLocalAccount(delegation.subject);Create final approval separately
const approval = await nuvouch.approvals.create({
targetSubject: { subjectId: delegation.subject },
subject: { id: localAccount.id },
source: { key: "stripe:payments", name: "Stripe Payments" },
action: {
type: "payment.prepare",
title: "Approve payment",
description: "Allow Stripe Payments to prepare the requested payment.",
},
actor: {
type: "ai_agent",
id: delegation.agent.id,
name: delegation.agent.name,
},
decisions: [
{ label: "Approve", value: "approve" },
{ label: "Deny", value: "deny" },
],
agentAuth: nuvouch.agentAuth.provider.buildAgentAuthApprovalContext(delegation),
});Use the Nuvouch CLI
The CLI is useful for local agent runtimes, demos, and controlled-beta provider testing. It stores runtime profiles in ~/.nuvouch/config.json.
| Field | Type | Description |
|---|---|---|
| nuvouch auth pair | command | Generate a runtime QR and claim the connection after mobile scan/code entry. |
| nuvouch auth status | command | Show the active local runtime profile. |
| nuvouch auth request | command | Request scoped delegation for an audience and purpose. |
| nuvouch auth wait | command | Wait for delegation approval and show redacted token state by default. |
| nuvouch auth token | command | Print an approved delegation token for provider calls. |
nuvouch auth pair \
--application-id app_openclaw \
--runtime-name "OpenClaw Desktop"
nuvouch auth request \
--audience aud_test_payments \
--scope payments.identity \
--scope payments.prepare \
--purpose "Identify the user before preparing a payment approval"
nuvouch auth wait del_123 --json
nuvouch auth token del_123