Agent Integration Guide
This guide explains how to integrate skills and agents with Clauth using the client SDK.
Installation
# From the clauth project
npm run build
Skills import from the clauth/client export:
import { ClauthClient } from "clauth/client";
Quick Start
import { ClauthClient } from "clauth/client";
// Auto-discovers from CLAUTH_DAEMON_URL, CLAUTH_SKILL_ID, CLAUTH_SKILL_TOKEN
const clauth = new ClauthClient();
// Make a brokered API call
const res = await clauth.fetch(
"github", // provider name
"github-main", // credential handle
"github:read", // required scope
"https://api.github.com/user"
);
console.log(res.status); // 200
console.log(res.body); // { login: "octocat", ... }
Configuration
Environment Variables (recommended)
export CLAUTH_DAEMON_URL=http://127.0.0.1:4317
export CLAUTH_SKILL_ID=my-agent
export CLAUTH_SKILL_TOKEN=clauth_sk_xxx
The client auto-discovers these:
const clauth = new ClauthClient(); // reads from env vars
Explicit Options
const clauth = new ClauthClient({
daemonUrl: "http://127.0.0.1:4317",
skillId: "my-agent",
skillToken: "clauth_sk_xxx"
});
API
clauth.fetch(provider, credential, scope, endpoint, options?)
Execute a brokered API request through the proxy.
Parameters:| Param | Type | Description |
provider | string | Provider name (e.g., "github", "slack") |
credential | string | Credential handle stored in the vault |
scope | string | Required scope (e.g., "github:read") |
endpoint | string | Full upstream URL |
options.method | string | HTTP method (default: "GET") |
options.headers | object | Additional headers (auth headers stripped) |
options.body | any | Request body (objects JSON-serialized) |
{ status: number, headers: object, body: any }
Examples:
// GET request
const user = await clauth.fetch(
"github", "github-main", "github:read",
"https://api.github.com/user"
);
// POST request with body
const issue = await clauth.fetch(
"github", "github-main", "github:write",
"https://api.github.com/repos/owner/repo/issues",
{
method: "POST",
body: { title: "Bug report", body: "Description here" }
}
);
// Custom headers
const res = await clauth.fetch(
"custom", "custom-api", "custom:read",
"https://api.custom.example.com/data",
{
headers: { "x-custom-header": "value" }
}
);
clauth.health()
Check if the daemon is reachable.
const ok = await clauth.health();
console.log(ok); // true
clauth.status()
Get daemon status information.
const info = await clauth.status();
console.log(info.vaultUnlocked); // true
console.log(info.activeGrants); // 5
console.log(info.auditIntegrity); // { valid: true, entries: 42 }
clauth.createIdentityChallenge(provider, accountId)
Create an identity verification challenge.
const challenge = await clauth.createIdentityChallenge("github", "octocat");
console.log(challenge.challengeId); // UUID
console.log(challenge.challenge); // base64url token
console.log(challenge.expiresAt); // ISO timestamp
clauth.verifyIdentity(challengeId, proof)
Submit proof for a challenge.
// For signed-challenge: proof is the credential handle
const result = await clauth.verifyIdentity(challenge.challengeId, "github-main");
console.log(result.status); // "verified"
console.log(result.verifiedAt); // ISO timestamp
// For email: proof is the challenge code
const result = await clauth.verifyIdentity(challenge.challengeId, challenge.challenge);
clauth.getIdentityStatus(challengeId)
Poll challenge status.
const status = await clauth.getIdentityStatus(challenge.challengeId);
console.log(status.status); // "pending" | "verified" | "expired" | "failed"
clauth.listIdentityProofs()
List verified identity proofs for this skill.
const proofs = await clauth.listIdentityProofs();
for (const proof of proofs) {
console.log(${proof.provider}/${proof.accountId}: ${proof.method});
}
Error Handling
All client methods throw ClauthError on failure:
import { ClauthClient, ClauthError } from "clauth/client";
try {
await clauth.fetch("github", "handle", "github:write", "https://api.github.com/user");
} catch (error) {
if (error instanceof ClauthError) {
console.error(error.code); // "ACCESS_DENIED"
console.error(error.message); // "No active grant..."
console.error(error.statusCode); // 403
}
}
Common error codes:
| Code | Meaning |
ACCESS_DENIED | Scope not granted or firewall blocked |
UNAUTHORIZED | Missing or invalid skill token |
NOT_FOUND | Credential handle or challenge not found |
VALIDATION_ERROR | Invalid request format |
UPSTREAM_ERROR | Provider API call failed |
Integration Patterns
Basic Agent Pattern
import { ClauthClient, ClauthError } from "clauth/client";
const clauth = new ClauthClient();
async function fetchGitHubRepos(): Promise<any[]> {
try {
const res = await clauth.fetch(
"github", "github-main", "github:read",
"https://api.github.com/user/repos"
);
return res.body as any[];
} catch (error) {
if (error instanceof ClauthError && error.code === "ACCESS_DENIED") {
console.log("Insufficient permissions for this operation");
return [];
}
throw error;
}
}
Multi-Provider Agent
const clauth = new ClauthClient();
// GitHub API
const repos = await clauth.fetch(
"github", "github-main", "github:read",
"https://api.github.com/user/repos"
);
// Slack API
const channels = await clauth.fetch(
"slack", "slack-bot", "slack:channels",
"https://slack.com/api/conversations.list"
);
// Post to Slack
await clauth.fetch(
"slack", "slack-bot", "slack:messages",
"https://slack.com/api/chat.postMessage",
{
method: "POST",
body: { channel: "C123", text: Found ${repos.body.length} repos }
}
);
Identity-Aware Agent
const clauth = new ClauthClient();
// Verify GitHub identity before performing sensitive operations
async function ensureVerified(): Promise<boolean> {
const proofs = await clauth.listIdentityProofs();
if (proofs.some(p => p.provider === "github")) {
return true;
}
const challenge = await clauth.createIdentityChallenge("github", "octocat");
const result = await clauth.verifyIdentity(challenge.challengeId, "github-main");
return result.status === "verified";
}
Polling Pattern for Identity
async function waitForVerification(challengeId: string, timeoutMs = 300000): Promise<boolean> {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
const status = await clauth.getIdentityStatus(challengeId);
if (status.status === "verified") return true;
if (status.status === "failed" || status.status === "expired") return false;
await new Promise(r => setTimeout(r, 2000));
}
return false;
}
Setup Checklist
For a new skill/agent to work with Clauth:
- Admin issues a skill token:
npm run cli -- skill-token issue --skill my-agent
- Admin stores required credentials:
npm run cli -- store --handle github-main --provider github --secret ghp_xxx
- Admin grants scopes:
npm run cli -- grant --skill my-agent --provider github --scope github:read --rpm 60
npm run cli -- grant --skill my-agent --provider github --scope github:write --rpm 30
- Skill configures environment:
export CLAUTH_SKILL_ID=my-agent
export CLAUTH_SKILL_TOKEN=<issued-token>
export CLAUTH_DAEMON_URL=http://127.0.0.1:4317
- Skill uses the client SDK to make brokered requests.
What Skills Cannot Do
- Read raw credentials (only the daemon resolves them)
- Bypass scope enforcement (grants checked before credential resolution)
- Send credentials to unauthorized hosts (endpoint allowlist enforced)
- Exceed rate limits (per-grant RPM ceiling)
- Hide abnormal behavior (firewall detects anomalies after warmup)
- Forge audit entries (hash chain prevents tampering)
- Forge identity proofs (HMAC-signed with vault master key)